| // | |
| // Copyright 2019 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // ProgramMtl.mm: | |
| // Implements the class methods for ProgramMtl. | |
| // | |
| #include "libANGLE/renderer/metal/ProgramMtl.h" | |
| #include <TargetConditionals.h> | |
| #include <sstream> | |
| #include <spirv_msl.hpp> | |
| #include "common/debug.h" | |
| #include "libANGLE/Context.h" | |
| #include "libANGLE/ProgramLinkedResources.h" | |
| #include "libANGLE/renderer/metal/ContextMtl.h" | |
| #include "libANGLE/renderer/metal/DisplayMtl.h" | |
| #include "libANGLE/renderer/metal/TextureMtl.h" | |
| #include "libANGLE/renderer/metal/mtl_glslang_utils.h" | |
| #include "libANGLE/renderer/metal/mtl_utils.h" | |
| #include "libANGLE/renderer/renderer_utils.h" | |
| namespace rx | |
| { | |
| 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, | |
| size_t *blockSizeOut) | |
| { | |
| if (uniforms.empty()) | |
| { | |
| *blockSizeOut = 0; | |
| return; | |
| } | |
| sh::Std140BlockEncoder blockEncoder; | |
| sh::GetUniformBlockInfo(uniforms, "", &blockEncoder, blockLayoutMapOut); | |
| size_t blockSize = blockEncoder.getCurrentOffset(); | |
| if (blockSize == 0) | |
| { | |
| *blockSizeOut = 0; | |
| return; | |
| } | |
| // Need to round up to multiple of vec4 | |
| *blockSizeOut = roundUp(blockSize, static_cast<size_t>(16)); | |
| return; | |
| } | |
| template <typename T> | |
| void UpdateDefaultUniformBlock(GLsizei count, | |
| uint32_t arrayIndex, | |
| int componentCount, | |
| const T *v, | |
| const sh::BlockMemberInfo &layoutInfo, | |
| angle::MemoryBuffer *uniformData) | |
| { | |
| const int elementSize = sizeof(T) * componentCount; | |
| uint8_t *dst = uniformData->data() + layoutInfo.offset; | |
| if (layoutInfo.arrayStride == 0 || layoutInfo.arrayStride == elementSize) | |
| { | |
| uint32_t arrayOffset = arrayIndex * layoutInfo.arrayStride; | |
| uint8_t *writePtr = dst + arrayOffset; | |
| ASSERT(writePtr + (elementSize * count) <= uniformData->data() + uniformData->size()); | |
| memcpy(writePtr, v, elementSize * count); | |
| } | |
| else | |
| { | |
| // Have to respect the arrayStride between each element of the array. | |
| int maxIndex = arrayIndex + count; | |
| for (int writeIndex = arrayIndex, readIndex = 0; writeIndex < maxIndex; | |
| writeIndex++, readIndex++) | |
| { | |
| const int arrayOffset = writeIndex * layoutInfo.arrayStride; | |
| uint8_t *writePtr = dst + arrayOffset; | |
| const T *readPtr = v + (readIndex * componentCount); | |
| ASSERT(writePtr + elementSize <= uniformData->data() + uniformData->size()); | |
| memcpy(writePtr, readPtr, elementSize); | |
| } | |
| } | |
| } | |
| 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, | |
| T *dst, | |
| const sh::BlockMemberInfo &layoutInfo, | |
| const angle::MemoryBuffer *uniformData) | |
| { | |
| ASSERT(layoutInfo.offset != -1); | |
| const int elementSize = sizeof(T) * componentCount; | |
| const uint8_t *source = uniformData->data() + layoutInfo.offset; | |
| if (layoutInfo.arrayStride == 0 || layoutInfo.arrayStride == elementSize) | |
| { | |
| const uint8_t *readPtr = source + arrayIndex * layoutInfo.arrayStride; | |
| memcpy(dst, readPtr, elementSize); | |
| } | |
| else | |
| { | |
| // Have to respect the arrayStride between each element of the array. | |
| const int arrayOffset = arrayIndex * layoutInfo.arrayStride; | |
| const uint8_t *readPtr = source + arrayOffset; | |
| memcpy(dst, readPtr, elementSize); | |
| } | |
| } | |
| class Std140BlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFactory | |
| { | |
| public: | |
| sh::BlockLayoutEncoder *makeEncoder() override { return new sh::Std140BlockEncoder(); } | |
| }; | |
| } // namespace | |
| // ProgramMtl implementation | |
| ProgramMtl::DefaultUniformBlock::DefaultUniformBlock() {} | |
| ProgramMtl::DefaultUniformBlock::~DefaultUniformBlock() = default; | |
| ProgramMtl::ProgramMtl(const gl::ProgramState &state) : ProgramImpl(state) {} | |
| ProgramMtl::~ProgramMtl() {} | |
| void ProgramMtl::destroy(const gl::Context *context) | |
| { | |
| auto contextMtl = mtl::GetImpl(context); | |
| reset(contextMtl); | |
| } | |
| void ProgramMtl::reset(ContextMtl *context) | |
| { | |
| for (auto &block : mDefaultUniformBlocks) | |
| { | |
| block.uniformLayout.clear(); | |
| } | |
| mMetalRenderPipelineCache.clear(); | |
| } | |
| std::unique_ptr<rx::LinkEvent> ProgramMtl::load(const gl::Context *context, | |
| gl::BinaryInputStream *stream, | |
| gl::InfoLog &infoLog) | |
| { | |
| // NOTE(hqle): support binary shader | |
| UNIMPLEMENTED(); | |
| return std::make_unique<LinkEventDone>(angle::Result::Stop); | |
| } | |
| void ProgramMtl::save(const gl::Context *context, gl::BinaryOutputStream *stream) | |
| { | |
| // NOTE(hqle): support binary shader | |
| UNIMPLEMENTED(); | |
| } | |
| void ProgramMtl::setBinaryRetrievableHint(bool retrievable) | |
| { | |
| UNIMPLEMENTED(); | |
| } | |
| void ProgramMtl::setSeparable(bool separable) | |
| { | |
| UNIMPLEMENTED(); | |
| } | |
| std::unique_ptr<LinkEvent> ProgramMtl::link(const gl::Context *context, | |
| const gl::ProgramLinkedResources &resources, | |
| gl::InfoLog &infoLog) | |
| { | |
| // Link resources before calling GetShaderSource to make sure they are ready for the set/binding | |
| // assignment done in that function. | |
| linkResources(resources); | |
| mtl::GlslangGetShaderSource(mState, resources, &mShaderSource); | |
| // NOTE(hqle): Parallelize linking. | |
| return std::make_unique<LinkEventDone>(linkImpl(context, infoLog)); | |
| } | |
| angle::Result ProgramMtl::linkImpl(const gl::Context *glContext, gl::InfoLog &infoLog) | |
| { | |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); | |
| // NOTE(hqle): No transform feedbacks for now, since we only support ES 2.0 atm | |
| reset(contextMtl); | |
| ANGLE_TRY(initDefaultUniformBlocks(glContext)); | |
| // Convert GLSL to spirv code | |
| gl::ShaderMap<std::vector<uint32_t>> shaderCodes; | |
| ANGLE_TRY(mtl::GlslangGetShaderSpirvCode(contextMtl, contextMtl->getCaps(), false, | |
| mShaderSource, &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])); | |
| return angle::Result::Continue; | |
| } | |
| void ProgramMtl::linkResources(const gl::ProgramLinkedResources &resources) | |
| { | |
| Std140BlockLayoutEncoderFactory std140EncoderFactory; | |
| gl::ProgramLinkedResourcesLinker linker(&std140EncoderFactory); | |
| linker.linkResources(mState, resources); | |
| } | |
| angle::Result ProgramMtl::initDefaultUniformBlocks(const gl::Context *glContext) | |
| { | |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); | |
| // Process vertex and fragment uniforms into std140 packing. | |
| gl::ShaderMap<sh::BlockLayoutMap> layoutMap; | |
| gl::ShaderMap<size_t> requiredBufferSize; | |
| requiredBufferSize.fill(0); | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| gl::Shader *shader = mState.getAttachedShader(shaderType); | |
| if (shader) | |
| { | |
| const std::vector<sh::Uniform> &uniforms = shader->getUniforms(); | |
| InitDefaultUniformBlock(uniforms, shader, &layoutMap[shaderType], | |
| &requiredBufferSize[shaderType]); | |
| } | |
| } | |
| // Init the default block layout info. | |
| const auto &uniforms = mState.getUniforms(); | |
| const auto &uniformLocations = mState.getUniformLocations(); | |
| for (size_t locSlot = 0; locSlot < uniformLocations.size(); ++locSlot) | |
| { | |
| const gl::VariableLocation &location = uniformLocations[locSlot]; | |
| gl::ShaderMap<sh::BlockMemberInfo> layoutInfo; | |
| if (location.used() && !location.ignored) | |
| { | |
| const gl::LinkedUniform &uniform = uniforms[location.index]; | |
| if (uniform.isInDefaultBlock() && !uniform.isSampler()) | |
| { | |
| std::string uniformName = uniform.name; | |
| if (uniform.isArray()) | |
| { | |
| // Gets the uniform name without the [0] at the end. | |
| uniformName = gl::ParseResourceName(uniformName, nullptr); | |
| } | |
| bool found = false; | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| auto it = layoutMap[shaderType].find(uniformName); | |
| if (it != layoutMap[shaderType].end()) | |
| { | |
| found = true; | |
| layoutInfo[shaderType] = it->second; | |
| } | |
| } | |
| ASSERT(found); | |
| } | |
| } | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| mDefaultUniformBlocks[shaderType].uniformLayout.push_back(layoutInfo[shaderType]); | |
| } | |
| } | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| if (requiredBufferSize[shaderType] > 0) | |
| { | |
| ASSERT(requiredBufferSize[shaderType] <= mtl::kDefaultUniformsMaxSize); | |
| if (!mDefaultUniformBlocks[shaderType].uniformData.resize( | |
| requiredBufferSize[shaderType])) | |
| { | |
| ANGLE_MTL_CHECK(contextMtl, false, GL_OUT_OF_MEMORY); | |
| } | |
| // Initialize uniform buffer memory to zero by default. | |
| mDefaultUniformBlocks[shaderType].uniformData.fill(0); | |
| mDefaultUniformBlocksDirty.set(shaderType); | |
| } | |
| } | |
| 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, | |
| const std::string &translatedMsl) | |
| { | |
| ANGLE_MTL_OBJC_SCOPE | |
| { | |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); | |
| DisplayMtl *display = contextMtl->getDisplay(); | |
| id<MTLDevice> mtlDevice = display->getMetalDevice(); | |
| // Convert to actual binary shader | |
| mtl::AutoObjCPtr<NSError *> err = nil; | |
| mtl::AutoObjCPtr<id<MTLLibrary>> mtlShaderLib = | |
| mtl::CreateShaderLibrary(mtlDevice, translatedMsl, &err); | |
| if (err && !mtlShaderLib) | |
| { | |
| std::ostringstream ss; | |
| ss << "Internal error compiling Metal shader:\n" | |
| << err.get().localizedDescription.UTF8String << "\n"; | |
| ERR() << ss.str(); | |
| infoLog << ss.str(); | |
| ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION); | |
| } | |
| auto mtlShader = [mtlShaderLib.get() newFunctionWithName:SHADER_ENTRY_NAME]; | |
| [mtlShader ANGLE_MTL_AUTORELEASE]; | |
| ASSERT(mtlShader); | |
| if (shaderType == gl::ShaderType::Vertex) | |
| { | |
| mMetalRenderPipelineCache.setVertexShader(contextMtl, mtlShader); | |
| } | |
| else if (shaderType == gl::ShaderType::Fragment) | |
| { | |
| mMetalRenderPipelineCache.setFragmentShader(contextMtl, mtlShader); | |
| } | |
| return angle::Result::Continue; | |
| } | |
| } | |
| GLboolean ProgramMtl::validate(const gl::Caps &caps, gl::InfoLog *infoLog) | |
| { | |
| // No-op. The spec is very vague about the behavior of validation. | |
| return GL_TRUE; | |
| } | |
| template <typename T> | |
| void ProgramMtl::setUniformImpl(GLint location, GLsizei count, const T *v, GLenum entryPointType) | |
| { | |
| const gl::VariableLocation &locationInfo = mState.getUniformLocations()[location]; | |
| const gl::LinkedUniform &linkedUniform = mState.getUniforms()[locationInfo.index]; | |
| if (linkedUniform.isSampler()) | |
| { | |
| // Sampler binding has changed. | |
| mSamplerBindingsDirty.set(); | |
| return; | |
| } | |
| if (linkedUniform.typeInfo->type == entryPointType) | |
| { | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| DefaultUniformBlock &uniformBlock = mDefaultUniformBlocks[shaderType]; | |
| const sh::BlockMemberInfo &layoutInfo = uniformBlock.uniformLayout[location]; | |
| // Assume an offset of -1 means the block is unused. | |
| if (layoutInfo.offset == -1) | |
| { | |
| continue; | |
| } | |
| const GLint componentCount = linkedUniform.typeInfo->componentCount; | |
| UpdateDefaultUniformBlock(count, locationInfo.arrayIndex, componentCount, v, layoutInfo, | |
| &uniformBlock.uniformData); | |
| mDefaultUniformBlocksDirty.set(shaderType); | |
| } | |
| } | |
| else | |
| { | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| DefaultUniformBlock &uniformBlock = mDefaultUniformBlocks[shaderType]; | |
| const sh::BlockMemberInfo &layoutInfo = uniformBlock.uniformLayout[location]; | |
| // Assume an offset of -1 means the block is unused. | |
| if (layoutInfo.offset == -1) | |
| { | |
| continue; | |
| } | |
| const GLint componentCount = linkedUniform.typeInfo->componentCount; | |
| ASSERT(linkedUniform.typeInfo->type == gl::VariableBoolVectorType(entryPointType)); | |
| GLint initialArrayOffset = | |
| locationInfo.arrayIndex * layoutInfo.arrayStride + layoutInfo.offset; | |
| for (GLint i = 0; i < count; i++) | |
| { | |
| GLint elementOffset = i * layoutInfo.arrayStride + initialArrayOffset; | |
| GLint *dest = | |
| reinterpret_cast<GLint *>(uniformBlock.uniformData.data() + elementOffset); | |
| const T *source = v + i * componentCount; | |
| for (int c = 0; c < componentCount; c++) | |
| { | |
| dest[c] = (source[c] == static_cast<T>(0)) ? GL_FALSE : GL_TRUE; | |
| } | |
| } | |
| mDefaultUniformBlocksDirty.set(shaderType); | |
| } | |
| } | |
| } | |
| template <typename T> | |
| void ProgramMtl::getUniformImpl(GLint location, T *v, GLenum entryPointType) const | |
| { | |
| const gl::VariableLocation &locationInfo = mState.getUniformLocations()[location]; | |
| const gl::LinkedUniform &linkedUniform = mState.getUniforms()[locationInfo.index]; | |
| ASSERT(!linkedUniform.isSampler()); | |
| const gl::ShaderType shaderType = linkedUniform.getFirstShaderTypeWhereActive(); | |
| ASSERT(shaderType != gl::ShaderType::InvalidEnum); | |
| const DefaultUniformBlock &uniformBlock = mDefaultUniformBlocks[shaderType]; | |
| const sh::BlockMemberInfo &layoutInfo = uniformBlock.uniformLayout[location]; | |
| ASSERT(linkedUniform.typeInfo->componentType == entryPointType || | |
| linkedUniform.typeInfo->componentType == gl::VariableBoolVectorType(entryPointType)); | |
| if (gl::IsMatrixType(linkedUniform.type)) | |
| { | |
| const uint8_t *ptrToElement = uniformBlock.uniformData.data() + layoutInfo.offset + | |
| (locationInfo.arrayIndex * layoutInfo.arrayStride); | |
| GetMatrixUniform(linkedUniform.type, v, reinterpret_cast<const T *>(ptrToElement), false); | |
| } | |
| else | |
| { | |
| ReadFromDefaultUniformBlock(linkedUniform.typeInfo->componentCount, locationInfo.arrayIndex, | |
| v, layoutInfo, &uniformBlock.uniformData); | |
| } | |
| } | |
| void ProgramMtl::setUniform1fv(GLint location, GLsizei count, const GLfloat *v) | |
| { | |
| setUniformImpl(location, count, v, GL_FLOAT); | |
| } | |
| void ProgramMtl::setUniform2fv(GLint location, GLsizei count, const GLfloat *v) | |
| { | |
| setUniformImpl(location, count, v, GL_FLOAT_VEC2); | |
| } | |
| void ProgramMtl::setUniform3fv(GLint location, GLsizei count, const GLfloat *v) | |
| { | |
| setUniformImpl(location, count, v, GL_FLOAT_VEC3); | |
| } | |
| void ProgramMtl::setUniform4fv(GLint location, GLsizei count, const GLfloat *v) | |
| { | |
| setUniformImpl(location, count, v, GL_FLOAT_VEC4); | |
| } | |
| void ProgramMtl::setUniform1iv(GLint startLocation, GLsizei count, const GLint *v) | |
| { | |
| setUniformImpl(startLocation, count, v, GL_INT); | |
| } | |
| void ProgramMtl::setUniform2iv(GLint location, GLsizei count, const GLint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_INT_VEC2); | |
| } | |
| void ProgramMtl::setUniform3iv(GLint location, GLsizei count, const GLint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_INT_VEC3); | |
| } | |
| void ProgramMtl::setUniform4iv(GLint location, GLsizei count, const GLint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_INT_VEC4); | |
| } | |
| void ProgramMtl::setUniform1uiv(GLint location, GLsizei count, const GLuint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_UNSIGNED_INT); | |
| } | |
| void ProgramMtl::setUniform2uiv(GLint location, GLsizei count, const GLuint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_UNSIGNED_INT_VEC2); | |
| } | |
| void ProgramMtl::setUniform3uiv(GLint location, GLsizei count, const GLuint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_UNSIGNED_INT_VEC3); | |
| } | |
| void ProgramMtl::setUniform4uiv(GLint location, GLsizei count, const GLuint *v) | |
| { | |
| setUniformImpl(location, count, v, GL_UNSIGNED_INT_VEC4); | |
| } | |
| template <int cols, int rows> | |
| void ProgramMtl::setUniformMatrixfv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| const gl::VariableLocation &locationInfo = mState.getUniformLocations()[location]; | |
| const gl::LinkedUniform &linkedUniform = mState.getUniforms()[locationInfo.index]; | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| DefaultUniformBlock &uniformBlock = mDefaultUniformBlocks[shaderType]; | |
| const sh::BlockMemberInfo &layoutInfo = uniformBlock.uniformLayout[location]; | |
| // Assume an offset of -1 means the block is unused. | |
| if (layoutInfo.offset == -1) | |
| { | |
| continue; | |
| } | |
| SetFloatUniformMatrixGLSL<cols, rows>::Run( | |
| locationInfo.arrayIndex, linkedUniform.getArraySizeProduct(), count, transpose, value, | |
| uniformBlock.uniformData.data() + layoutInfo.offset); | |
| mDefaultUniformBlocksDirty.set(shaderType); | |
| } | |
| } | |
| void ProgramMtl::setUniformMatrix2fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<2, 2>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix3fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<3, 3>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix4fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<4, 4>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix2x3fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<2, 3>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix3x2fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<3, 2>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix2x4fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<2, 4>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix4x2fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<4, 2>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix3x4fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<3, 4>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setUniformMatrix4x3fv(GLint location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const GLfloat *value) | |
| { | |
| setUniformMatrixfv<4, 3>(location, count, transpose, value); | |
| } | |
| void ProgramMtl::setPathFragmentInputGen(const std::string &inputName, | |
| GLenum genMode, | |
| GLint components, | |
| const GLfloat *coeffs) | |
| { | |
| UNIMPLEMENTED(); | |
| } | |
| void ProgramMtl::getUniformfv(const gl::Context *context, GLint location, GLfloat *params) const | |
| { | |
| getUniformImpl(location, params, GL_FLOAT); | |
| } | |
| void ProgramMtl::getUniformiv(const gl::Context *context, GLint location, GLint *params) const | |
| { | |
| getUniformImpl(location, params, GL_INT); | |
| } | |
| void ProgramMtl::getUniformuiv(const gl::Context *context, GLint location, GLuint *params) const | |
| { | |
| getUniformImpl(location, params, GL_UNSIGNED_INT); | |
| } | |
| angle::Result ProgramMtl::setupDraw(const gl::Context *glContext, | |
| mtl::RenderCommandEncoder *cmdEncoder, | |
| const Optional<mtl::RenderPipelineDesc> &changedPipelineDescOpt, | |
| bool forceTexturesSetting) | |
| { | |
| ContextMtl *context = mtl::GetImpl(glContext); | |
| if (changedPipelineDescOpt.valid()) | |
| { | |
| const auto &changedPipelineDesc = changedPipelineDescOpt.value(); | |
| // Render pipeline state needs to be changed | |
| id<MTLRenderPipelineState> pipelineState = | |
| mMetalRenderPipelineCache.getRenderPipelineState(context, changedPipelineDesc); | |
| if (!pipelineState) | |
| { | |
| // Error already logged inside getRenderPipelineState() | |
| return angle::Result::Stop; | |
| } | |
| cmdEncoder->setRenderPipelineState(pipelineState); | |
| // We need to rebind uniform buffers & textures also | |
| mDefaultUniformBlocksDirty.set(); | |
| mSamplerBindingsDirty.set(); | |
| } | |
| ANGLE_TRY(commitUniforms(context, cmdEncoder)); | |
| ANGLE_TRY(updateTextures(glContext, cmdEncoder, forceTexturesSetting)); | |
| return angle::Result::Continue; | |
| } | |
| angle::Result ProgramMtl::commitUniforms(ContextMtl *context, mtl::RenderCommandEncoder *cmdEncoder) | |
| { | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| if (!mDefaultUniformBlocksDirty[shaderType]) | |
| { | |
| continue; | |
| } | |
| DefaultUniformBlock &uniformBlock = mDefaultUniformBlocks[shaderType]; | |
| if (!uniformBlock.uniformData.size()) | |
| { | |
| continue; | |
| } | |
| switch (shaderType) | |
| { | |
| case gl::ShaderType::Vertex: | |
| cmdEncoder->setVertexBytes(uniformBlock.uniformData.data(), | |
| uniformBlock.uniformData.size(), | |
| mtl::kDefaultUniformsBindingIndex); | |
| break; | |
| case gl::ShaderType::Fragment: | |
| cmdEncoder->setFragmentBytes(uniformBlock.uniformData.data(), | |
| uniformBlock.uniformData.size(), | |
| mtl::kDefaultUniformsBindingIndex); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| mDefaultUniformBlocksDirty.reset(shaderType); | |
| } | |
| return angle::Result::Continue; | |
| } | |
| angle::Result ProgramMtl::updateTextures(const gl::Context *glContext, | |
| mtl::RenderCommandEncoder *cmdEncoder, | |
| bool forceUpdate) | |
| { | |
| const auto &glState = glContext->getState(); | |
| const gl::ActiveTexturePointerArray &completeTextures = glState.getActiveTexturesCache(); | |
| for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) | |
| { | |
| if (!mSamplerBindingsDirty[shaderType] && !forceUpdate) | |
| { | |
| continue; | |
| } | |
| for (uint32_t textureIndex = 0; textureIndex < mState.getSamplerBindings().size(); | |
| ++textureIndex) | |
| { | |
| const gl::SamplerBinding &samplerBinding = mState.getSamplerBindings()[textureIndex]; | |
| ASSERT(!samplerBinding.unreferenced); | |
| for (uint32_t arrayElement = 0; arrayElement < samplerBinding.boundTextureUnits.size(); | |
| ++arrayElement) | |
| { | |
| GLuint textureUnit = samplerBinding.boundTextureUnits[arrayElement]; | |
| gl::Texture *texture = completeTextures[textureUnit]; | |
| auto destBindingPoint = textureIndex + arrayElement; | |
| if (!texture) | |
| { | |
| BindNullSampler(glContext, cmdEncoder, shaderType, samplerBinding.textureType, | |
| destBindingPoint); | |
| continue; | |
| } | |
| TextureMtl *textureMtl = mtl::GetImpl(texture); | |
| switch (shaderType) | |
| { | |
| case gl::ShaderType::Vertex: | |
| ANGLE_TRY(textureMtl->bindVertexShader(glContext, cmdEncoder, | |
| destBindingPoint, destBindingPoint)); | |
| break; | |
| case gl::ShaderType::Fragment: | |
| ANGLE_TRY(textureMtl->bindFragmentShader( | |
| glContext, cmdEncoder, destBindingPoint, destBindingPoint)); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } // for array elements | |
| } // for sampler bindings | |
| } // for shader types | |
| return angle::Result::Continue; | |
| } | |
| } // namespace rx |