/** * Copyright (c) 2006-2020 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ #include "Shader.h" #include "Graphics.h" #include "common/int.h" // glslang #include "libraries/glslang/glslang/Public/ShaderLang.h" #include "libraries/glslang/SPIRV/GlslangToSpv.h" #include "libraries/spirv_cross/spirv_msl.hpp" #include "libraries/spirv_cross/spirv_reflect.hpp" #include namespace love { namespace graphics { namespace metal { static_assert(MAX_COLOR_RENDER_TARGETS <= 8, "Metal pipeline cache key only stores 8 render target pixel formats."); // TODO: Use love.graphics to determine actual limits? static const TBuiltInResource defaultTBuiltInResource = { /* .MaxLights = */ 32, /* .MaxClipPlanes = */ 6, /* .MaxTextureUnits = */ 32, /* .MaxTextureCoords = */ 32, /* .MaxVertexAttribs = */ 64, /* .MaxVertexUniformComponents = */ 16384, /* .MaxVaryingFloats = */ 128, /* .MaxVertexTextureImageUnits = */ 32, /* .MaxCombinedTextureImageUnits = */ 80, /* .MaxTextureImageUnits = */ 32, /* .MaxFragmentUniformComponents = */ 16384, /* .MaxDrawBuffers = */ 8, /* .MaxVertexUniformVectors = */ 4096, /* .MaxVaryingVectors = */ 32, /* .MaxFragmentUniformVectors = */ 4096, /* .MaxVertexOutputVectors = */ 32, /* .MaxFragmentInputVectors = */ 31, /* .MinProgramTexelOffset = */ -8, /* .MaxProgramTexelOffset = */ 7, /* .MaxClipDistances = */ 8, /* .MaxComputeWorkGroupCountX = */ 65535, /* .MaxComputeWorkGroupCountY = */ 65535, /* .MaxComputeWorkGroupCountZ = */ 65535, /* .MaxComputeWorkGroupSizeX = */ 1024, /* .MaxComputeWorkGroupSizeY = */ 1024, /* .MaxComputeWorkGroupSizeZ = */ 64, /* .MaxComputeUniformComponents = */ 1024, /* .MaxComputeTextureImageUnits = */ 32, /* .MaxComputeImageUniforms = */ 16, /* .MaxComputeAtomicCounters = */ 4096, /* .MaxComputeAtomicCounterBuffers = */ 8, /* .MaxVaryingComponents = */ 128, /* .MaxVertexOutputComponents = */ 128, /* .MaxGeometryInputComponents = */ 128, /* .MaxGeometryOutputComponents = */ 128, /* .MaxFragmentInputComponents = */ 128, /* .MaxImageUnits = */ 192, /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144, /* .MaxCombinedShaderOutputResources = */ 144, /* .MaxImageSamples = */ 32, /* .MaxVertexImageUniforms = */ 16, /* .MaxTessControlImageUniforms = */ 16, /* .MaxTessEvaluationImageUniforms = */ 16, /* .MaxGeometryImageUniforms = */ 16, /* .MaxFragmentImageUniforms = */ 16, /* .MaxCombinedImageUniforms = */ 80, /* .MaxGeometryTextureImageUnits = */ 16, /* .MaxGeometryOutputVertices = */ 256, /* .MaxGeometryTotalOutputComponents = */ 1024, /* .MaxGeometryUniformComponents = */ 1024, /* .MaxGeometryVaryingComponents = */ 64, /* .MaxTessControlInputComponents = */ 128, /* .MaxTessControlOutputComponents = */ 128, /* .MaxTessControlTextureImageUnits = */ 16, /* .MaxTessControlUniformComponents = */ 1024, /* .MaxTessControlTotalOutputComponents = */ 4096, /* .MaxTessEvaluationInputComponents = */ 128, /* .MaxTessEvaluationOutputComponents = */ 128, /* .MaxTessEvaluationTextureImageUnits = */ 16, /* .MaxTessEvaluationUniformComponents = */ 1024, /* .MaxTessPatchComponents = */ 120, /* .MaxPatchVertices = */ 32, /* .MaxTessGenLevel = */ 64, /* .MaxViewports = */ 16, /* .MaxVertexAtomicCounters = */ 4096, /* .MaxTessControlAtomicCounters = */ 4096, /* .MaxTessEvaluationAtomicCounters = */ 4096, /* .MaxGeometryAtomicCounters = */ 4096, /* .MaxFragmentAtomicCounters = */ 4096, /* .MaxCombinedAtomicCounters = */ 4096, /* .MaxAtomicCounterBindings = */ 8, /* .MaxVertexAtomicCounterBuffers = */ 8, /* .MaxTessControlAtomicCounterBuffers = */ 8, /* .MaxTessEvaluationAtomicCounterBuffers = */ 8, /* .MaxGeometryAtomicCounterBuffers = */ 8, /* .MaxFragmentAtomicCounterBuffers = */ 8, /* .MaxCombinedAtomicCounterBuffers = */ 8, /* .MaxAtomicCounterBufferSize = */ 16384, /* .MaxTransformFeedbackBuffers = */ 4, /* .MaxTransformFeedbackInterleavedComponents = */ 64, /* .MaxCullDistances = */ 8, /* .MaxCombinedClipAndCullDistances = */ 8, /* .MaxSamples = */ 32, /* .maxMeshOutputVerticesNV = */ 256, /* .maxMeshOutputPrimitivesNV = */ 512, /* .maxMeshWorkGroupSizeX_NV = */ 32, /* .maxMeshWorkGroupSizeY_NV = */ 1, /* .maxMeshWorkGroupSizeZ_NV = */ 1, /* .maxTaskWorkGroupSizeX_NV = */ 32, /* .maxTaskWorkGroupSizeY_NV = */ 1, /* .maxTaskWorkGroupSizeZ_NV = */ 1, /* .maxMeshViewCountNV = */ 4, /* .maxDualSourceDrawBuffersEXT = */ 1, /* .limits = */ { /* .nonInductiveForLoops = */ 1, /* .whileLoops = */ 1, /* .doWhileLoops = */ 1, /* .generalUniformIndexing = */ 1, /* .generalAttributeMatrixVectorIndexing = */ 1, /* .generalVaryingIndexing = */ 1, /* .generalSamplerIndexing = */ 1, /* .generalVariableIndexing = */ 1, /* .generalConstantMatrixVectorIndexing = */ 1, } }; static MTLVertexFormat getMTLVertexFormat(DataFormat format) { switch (format) { case DATAFORMAT_FLOAT: return MTLVertexFormatFloat; case DATAFORMAT_FLOAT_VEC2: return MTLVertexFormatFloat2; case DATAFORMAT_FLOAT_VEC3: return MTLVertexFormatFloat3; case DATAFORMAT_FLOAT_VEC4: return MTLVertexFormatFloat4; case DATAFORMAT_INT32: return MTLVertexFormatInt; case DATAFORMAT_INT32_VEC2: return MTLVertexFormatInt2; case DATAFORMAT_INT32_VEC3: return MTLVertexFormatInt3; case DATAFORMAT_INT32_VEC4: return MTLVertexFormatInt4; case DATAFORMAT_UINT32: return MTLVertexFormatUInt; case DATAFORMAT_UINT32_VEC2: return MTLVertexFormatUInt2; case DATAFORMAT_UINT32_VEC3: return MTLVertexFormatUInt3; case DATAFORMAT_UINT32_VEC4: return MTLVertexFormatUInt4; case DATAFORMAT_SNORM8_VEC4: return MTLVertexFormatChar4Normalized; case DATAFORMAT_UNORM8_VEC4: return MTLVertexFormatUChar4Normalized; case DATAFORMAT_INT8_VEC4: return MTLVertexFormatChar4; case DATAFORMAT_UINT8_VEC4: return MTLVertexFormatUChar4; case DATAFORMAT_SNORM16_VEC2: return MTLVertexFormatShort2Normalized; case DATAFORMAT_SNORM16_VEC4: return MTLVertexFormatShort4Normalized; case DATAFORMAT_UNORM16_VEC2: return MTLVertexFormatUShort2Normalized; case DATAFORMAT_UNORM16_VEC4: return MTLVertexFormatUShort4Normalized; case DATAFORMAT_INT16_VEC2: return MTLVertexFormatShort2; case DATAFORMAT_INT16_VEC4: return MTLVertexFormatShort4; case DATAFORMAT_UINT16: return MTLVertexFormatUShort; case DATAFORMAT_UINT16_VEC2: return MTLVertexFormatUShort2; case DATAFORMAT_UINT16_VEC4: return MTLVertexFormatUShort4; default: return MTLVertexFormatInvalid; } } static MTLBlendOperation getMTLBlendOperation(BlendOperation op) { switch (op) { case BLENDOP_ADD: return MTLBlendOperationAdd; case BLENDOP_SUBTRACT: return MTLBlendOperationSubtract; case BLENDOP_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract; case BLENDOP_MIN: return MTLBlendOperationMin; case BLENDOP_MAX: return MTLBlendOperationMax; case BLENDOP_MAX_ENUM: return MTLBlendOperationAdd; } return MTLBlendOperationAdd; } static MTLBlendFactor getMTLBlendFactor(BlendFactor factor) { switch (factor) { case BLENDFACTOR_ZERO: return MTLBlendFactorZero; case BLENDFACTOR_ONE: return MTLBlendFactorOne; case BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor; case BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor; case BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha; case BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha; case BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor; case BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor; case BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha; case BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha; case BLENDFACTOR_SRC_ALPHA_SATURATED: return MTLBlendFactorSourceAlphaSaturated; case BLENDFACTOR_MAX_ENUM: return MTLBlendFactorZero; } return MTLBlendFactorZero; } static inline id getMTLTexture(love::graphics::Texture *tex) { return tex ? (__bridge id)(void *) tex->getHandle() : nil; } static inline id getMTLTexture(love::graphics::Buffer *buffer) { return buffer ? (__bridge id)(void *) buffer->getTexelBufferHandle() : nil; } static inline id getMTLSampler(love::graphics::Texture *tex) { return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; } static EShLanguage getGLSLangStage(ShaderStage::StageType stage) { switch (stage) { case ShaderStage::STAGE_VERTEX: return EShLangVertex; case ShaderStage::STAGE_PIXEL: return EShLangFragment; case ShaderStage::STAGE_MAX_ENUM: return EShLangCount; } return EShLangCount; } Shader::Shader(id device, love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) : love::graphics::Shader(vertex, pixel) , functions() , builtinUniformInfo() , localUniformBufferData(nullptr) , localUniformBufferSize(0) , builtinUniformDataOffset(0) { @autoreleasepool { using namespace glslang; TShader *glslangShaders[ShaderStage::STAGE_MAX_ENUM] = {}; TProgram *program = new TProgram(); auto cleanup = [&]() { delete program; for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++) delete glslangShaders[i]; }; // We can't do this in ShaderStage because the mapIO call modifies the // TShader internals in a manner that prevents it from being shared. for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++) { if (!stages[i]) continue; auto stage = (ShaderStage::StageType) i; auto glslangstage = getGLSLangStage(stage); auto tshader = new TShader(glslangstage); glslangShaders[i] = tshader; tshader->setEnvInput(EShSourceGlsl, glslangstage, EShClientVulkan, 450); tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2); tshader->setEnvTarget(EShTargetSpv, EShTargetSpv_1_5); tshader->setAutoMapLocations(true); tshader->setAutoMapBindings(true); // Needed for local uniforms to work (they will be converted into a // uniform block). // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_vulkan_glsl_relaxed.txt tshader->setEnvInputVulkanRulesRelaxed(); tshader->setGlobalUniformBinding(0); tshader->setGlobalUniformSet(0); const std::string &source = stages[i]->getSource(); const char *csrc = source.c_str(); int srclen = (int) source.length(); tshader->setStringsWithLengths(&csrc, &srclen, 1); int defaultversion = 450; EProfile defaultprofile = ECoreProfile; bool forcedefault = false; bool forwardcompat = true; if (!tshader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings)) { const char *stagename = "unknown"; ShaderStage::getConstant(stage, stagename); std::string err = "Error parsing " + std::string(stagename) + " shader:\n\n" + std::string(tshader->getInfoLog()) + "\n" + std::string(tshader->getInfoDebugLog()); cleanup(); throw love::Exception("%s", err.c_str()); } program->addShader(tshader); } if (!program->link(EShMsgDefault)) { //err = "Cannot compile shader:\n\n" + std::string(program->getInfoLog()) + "\n" + std::string(program->getInfoDebugLog()); cleanup(); throw love::Exception("link failed! %s\n", program->getInfoLog()); } if (!program->mapIO()) { cleanup(); throw love::Exception("mapIO failed"); } try { compileFromGLSLang(device, *program); } catch (love::Exception &) { cleanup(); throw; } cleanup(); }} void Shader::compileFromGLSLang(id device, const glslang::TProgram &program) { using namespace glslang; using namespace spirv_cross; auto gfx = Graphics::getInstance(); std::map varyings; int nextVaryingLocation = 0; for (int stageindex = 0; stageindex < ShaderStage::STAGE_MAX_ENUM; stageindex++) { auto glslangstage = getGLSLangStage((ShaderStage::StageType) stageindex); auto intermediate = program.getIntermediate(glslangstage); if (intermediate == nullptr) continue; spv::SpvBuildLogger logger; glslang::SpvOptions opt; opt.validate = true; std::vector spirv; GlslangToSpv(*intermediate, spirv, &logger, &opt); std::string msgs = logger.getAllMessages(); // printf("spirv length: %ld, messages:\n%s\n", spirv.size(), msgs.c_str()); try { // printf("GLSL INPUT SOURCE:\n\n%s\n\n", pixel->getSource().c_str()); CompilerMSL msl(std::move(spirv)); auto interfacevars = msl.get_active_interface_variables(); msl.set_enabled_interface_variables(interfacevars); ShaderResources resources = msl.get_shader_resources(); for (const auto &resource : resources.sampled_images) { const SPIRType &basetype = msl.get_type(resource.base_type_id); const SPIRType &type = msl.get_type(resource.type_id); const SPIRType &imagetype = msl.get_type(basetype.image.type); UniformInfo u = {}; u.baseType = UNIFORM_SAMPLER; u.name = resource.name; u.count = type.array.empty() ? 1 : type.array[0]; u.isDepthSampler = type.image.depth; switch (imagetype.basetype) { case SPIRType::Float: u.dataBaseType = DATA_BASETYPE_FLOAT; break; case SPIRType::Int: u.dataBaseType = DATA_BASETYPE_INT; break; case SPIRType::UInt: u.dataBaseType = DATA_BASETYPE_UINT; break; default: break; } switch (basetype.image.dim) { case spv::Dim2D: u.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D; u.textures = new love::graphics::Texture*[u.count]; break; case spv::Dim3D: u.textureType = TEXTURE_VOLUME; u.textures = new love::graphics::Texture*[u.count]; break; case spv::DimCube: if (basetype.image.arrayed) throw love::Exception("Cubemap Arrays are not currently supported."); u.textureType = TEXTURE_CUBE; u.textures = new love::graphics::Texture*[u.count]; break; case spv::DimBuffer: u.baseType = UNIFORM_TEXELBUFFER; u.buffers = new love::graphics::Buffer*[u.count]; break; default: // TODO: error? continue? break; } u.data = malloc(sizeof(int) * u.count); for (int i = 0; i < u.count; i++) u.ints[i] = -1; // Initialized below, after compiling. if (u.baseType == UNIFORM_SAMPLER) { auto tex = gfx->getDefaultTexture(u.textureType); for (int i = 0; i < u.count; i++) { tex->retain(); u.textures[i] = tex; } } else if (u.baseType == UNIFORM_TEXELBUFFER) { for (int i = 0; i < u.count; i++) u.buffers[i] = nullptr; // TODO } uniforms[u.name] = u; BuiltinUniform builtin; if (getConstant(resource.name.c_str(), builtin)) builtinUniformInfo[builtin] = &uniforms[u.name]; } for (const auto &resource : resources.uniform_buffers) { if (resource.name == "gl_DefaultUniformBlock") { MSLResourceBinding binding; binding.stage = msl.get_execution_model(); binding.binding = msl.get_decoration(resource.id, spv::DecorationBinding); binding.desc_set = msl.get_decoration(resource.id, spv::DecorationDescriptorSet); binding.msl_buffer = getUniformBufferBinding(); msl.add_msl_resource_binding(binding); const SPIRType &type = msl.get_type(resource.base_type_id); const auto &membertypes = type.member_types; size_t size = msl.get_declared_struct_size(type); if (localUniformBufferSize != 0) { if (localUniformBufferSize != size) throw love::Exception("Local uniform buffer size mismatch"); continue; } localUniformBufferData = new uint8[size]; localUniformBufferSize = size; memset(localUniformBufferData, 0, size); for (size_t uindex = 0; uindex < membertypes.size(); uindex++) { const auto &membertype = msl.get_type(membertypes[uindex]); size_t membersize = msl.get_declared_struct_member_size(type, uindex); size_t offset = msl.type_struct_member_offset(type, uindex); UniformInfo u = {}; u.name = msl.get_name(membertypes[uindex]); u.dataSize = membersize; u.count = membertype.array.empty() ? 1 : membertype.array[0]; switch (membertype.basetype) { case SPIRType::Int: case SPIRType::UInt: case SPIRType::Float: u.data = localUniformBufferData + offset; if (membertype.columns == 1) { if (membertype.basetype == SPIRType::Int) u.baseType = UNIFORM_INT; else if (membertype.basetype == SPIRType::UInt) u.baseType = UNIFORM_UINT; else u.baseType = UNIFORM_FLOAT; u.components = membertype.vecsize; } else { u.baseType = UNIFORM_MATRIX; u.matrix.rows = membertype.vecsize; u.matrix.columns = membertype.columns; } break; case SPIRType::Struct: // TODO break; default: break; } uniforms[u.name] = u; BuiltinUniform builtin = BUILTIN_MAX_ENUM; if (getConstant(u.name.c_str(), builtin)) { if (builtin == BUILTIN_UNIFORMS_PER_DRAW) builtinUniformDataOffset = offset; builtinUniformInfo[builtin] = &uniforms[u.name]; } } } } for (const auto &resource : resources.storage_buffers) { auto it = uniforms.find(resource.name); if (it != uniforms.end()) { continue; } // TODO } if (stageindex == ShaderStage::STAGE_VERTEX) { int nextattributeindex = ATTRIB_MAX_ENUM; for (const auto &var : interfacevars) { spv::StorageClass storage = msl.get_storage_class(var); const std::string &name = msl.get_name(var); if (storage == spv::StorageClassInput) { int index = 0; BuiltinVertexAttribute builtinattribute; if (graphics::getConstant(name.c_str(), builtinattribute)) index = (int) builtinattribute; else index = nextattributeindex++; msl.set_decoration(var, spv::DecorationLocation, index); attributes[name] = msl.get_decoration(var, spv::DecorationLocation); } } for (const auto &varying : resources.stage_outputs) { // printf("vertex shader output %s: %d\n", inp.name.c_str(), msl.get_decoration(inp.id, spv::DecorationLocation)); varyings[varying.name] = nextVaryingLocation; msl.set_decoration(varying.id, spv::DecorationLocation, nextVaryingLocation++); } } else if (stageindex == ShaderStage::STAGE_PIXEL) { for (const auto &varying : resources.stage_inputs) { const auto it = varyings.find(varying.name); if (it != varyings.end()) msl.set_decoration(varying.id, spv::DecorationLocation, it->second); } } // printf("// ubos: %ld, storage: %ld, inputs: %ld, outputs: %ld, images: %ld, samplers: %ld, push: %ld\n", resources.uniform_buffers.size(), resources.storage_buffers.size(), resources.stage_inputs.size(), resources.stage_outputs.size(), resources.storage_images.size(), resources.sampled_images.size(), resources.push_constant_buffers.size()); CompilerMSL::Options options; options.set_msl_version(2, 1); options.texture_buffer_native = true; #ifdef LOVE_IOS options.platform = CompilerMSL::Options::iOS; #else options.platform = CompilerMSL::Options::macOS; #endif msl.set_msl_options(options); std::string source = msl.compile(); // printf("// MSL SOURCE for stage %d:\n\n%s\n\n", stageindex, source.c_str()); NSString *nssource = [[NSString alloc] initWithBytes:source.c_str() length:source.length() encoding:NSUTF8StringEncoding]; NSError *err = nil; id library = [device newLibraryWithSource:nssource options:nil error:&err]; if (library == nil && err != nil) { NSLog(@"errors: %@", err); throw love::Exception("Error compiling converted Metal shader code"); } functions[stageindex] = [library newFunctionWithName:library.functionNames[0]]; for (const auto &resource : resources.sampled_images) { auto it = uniforms.find(resource.name); if (it == uniforms.end()) continue; UniformInfo &u = it->second; uint32 texturebinding = msl.get_automatic_msl_resource_binding(resource.id); uint32 samplerbinding = msl.get_automatic_msl_resource_binding_secondary(resource.id); if (texturebinding == (uint32)-1) continue; for (int i = 0; i < u.count; i++) { if (u.ints[i] == -1) { u.ints[i] = (int)textureBindings.size(); TextureBinding b = {}; if (u.baseType == UNIFORM_TEXELBUFFER) { // TODO } else { b.texture = getMTLTexture(u.textures[i]); b.sampler = getMTLSampler(u.textures[i]); BuiltinUniform builtin = BUILTIN_MAX_ENUM; if (getConstant(u.name.c_str(), builtin) && builtin == BUILTIN_TEXTURE_MAIN) b.isMainTexture = true; } for (uint8 &stagebinding : b.texturestages) stagebinding = LOVE_UINT8_MAX; for (uint8 &stagebinding : b.samplerstages) stagebinding = LOVE_UINT8_MAX; textureBindings.push_back(b); } auto &b = textureBindings[u.ints[i]]; b.texturestages[stageindex] = (uint8) texturebinding; b.samplerstages[stageindex] = (uint8) samplerbinding; } } } catch (std::exception &e) { printf("Error parsing SPIR-V shader source: %s\n", e.what()); } } } Shader::~Shader() { @autoreleasepool { for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++) functions[i] = nil; for (const auto &kvp : cachedRenderPipelines) CFBridgingRelease(kvp.second); cachedRenderPipelines.clear(); for (const auto &it : uniforms) { const auto &u = it.second; if (u.baseType == UNIFORM_SAMPLER) { free(u.data); for (int i = 0; i < u.count; i++) { if (u.textures[i] != nullptr) u.textures[i]->release(); } delete[] u.textures; } else if (u.baseType == UNIFORM_TEXELBUFFER || u.baseType == UNIFORM_STORAGEBUFFER) { free(u.data); for (int i = 0; i < u.count; i++) { if (u.buffers[i] != nullptr) u.buffers[i]->release(); } delete[] u.buffers; } } delete[] localUniformBufferData; }} void Shader::attach() { if (current != this) { Graphics *gfx = Graphics::getInstance(); gfx->flushBatchedDraws(); gfx->attachShader(this); current = this; } } int Shader::getVertexAttributeIndex(const std::string &name) { const auto it = attributes.find(name); return it != attributes.end() ? it->second : -1; } const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const { const auto it = uniforms.find(name); return it != uniforms.end() ? &(it->second) : nullptr; } const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const { return builtinUniformInfo[(int)builtin]; } void Shader::updateUniform(const UniformInfo * /*info*/, int /*count*/) { // Nothing needed here, All uniform data will be memcpy'd to the main // uniform buffer before drawing. // FIXME: do we need to copy to a second buffer here, in order for batch // flushing to work (and possibly padding)? } void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) { @autoreleasepool { if (info->baseType != UNIFORM_SAMPLER) return; if (current == this) Graphics::flushBatchedDrawsGlobal(); count = std::min(count, info->count); for (int i = 0; i < count; i++) { love::graphics::Texture *tex = textures[i]; if (tex != nullptr) { if (!validateTexture(info, tex, false)) continue; } else { auto gfx = Graphics::getInstance(); tex = gfx->getDefaultTexture(info->textureType); } tex->retain(); if (info->textures[i] != nullptr) info->textures[i]->release(); info->textures[i] = tex; // TODO: handle changing a sampler after Shader:send(texture)... textureBindings[info->ints[i]].texture = getMTLTexture(tex); textureBindings[info->ints[i]].sampler = getMTLSampler(tex); } }} void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) { bool texelbinding = info->baseType == UNIFORM_TEXELBUFFER; bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER; if (!texelbinding && !storagebinding) return; if (current == this) Graphics::flushBatchedDrawsGlobal(); count = std::min(count, info->count); // Bind the textures to the texture units. for (int i = 0; i < count; i++) { love::graphics::Buffer *buffer = buffers[i]; if (buffer != nullptr) { if (!validateBuffer(info, buffer, false)) continue; buffer->retain(); } if (info->buffers[i] != nullptr) info->buffers[i]->release(); info->buffers[i] = buffer; if (texelbinding) { textureBindings[info->ints[i]].texture = getMTLTexture(buffer); } else if (storagebinding) { // TODO } } } void Shader::setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) { // TODO } bool Shader::hasUniform(const std::string &name) const { return uniforms.find(name) != uniforms.end(); } id Shader::getCachedRenderPipeline(const RenderPipelineKey &key) { auto it = cachedRenderPipelines.find(key); if (it != cachedRenderPipelines.end()) return (__bridge id) it->second; id device = Graphics::getInstance()->device; MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new]; desc.vertexFunction = functions[ShaderStage::STAGE_VERTEX]; desc.fragmentFunction = functions[ShaderStage::STAGE_PIXEL]; desc.rasterSampleCount = std::max((int) key.msaa, 1); for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++) { PixelFormat format = (PixelFormat)((key.colorRenderTargetFormats >> (i * 8)) & 0xFF); if (format == PIXELFORMAT_UNKNOWN) continue; MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i]; bool isSRGB = false; auto formatdesc = Metal::convertPixelFormat(format, isSRGB); attachment.pixelFormat = formatdesc.format; if (key.blend.enable) { attachment.blendingEnabled = YES; attachment.sourceRGBBlendFactor = getMTLBlendFactor(key.blend.srcFactorRGB); attachment.destinationRGBBlendFactor = getMTLBlendFactor(key.blend.dstFactorRGB); attachment.rgbBlendOperation = getMTLBlendOperation(key.blend.operationRGB); attachment.sourceAlphaBlendFactor = getMTLBlendFactor(key.blend.srcFactorA); attachment.destinationAlphaBlendFactor = getMTLBlendFactor(key.blend.dstFactorA); attachment.alphaBlendOperation = getMTLBlendOperation(key.blend.operationA); } MTLColorWriteMask writeMask = MTLColorWriteMaskNone; if (key.colorChannelMask.r) writeMask |= MTLColorWriteMaskRed; if (key.colorChannelMask.g) writeMask |= MTLColorWriteMaskGreen; if (key.colorChannelMask.b) writeMask |= MTLColorWriteMaskBlue; if (key.colorChannelMask.a) writeMask |= MTLColorWriteMaskAlpha; attachment.writeMask = writeMask; desc.colorAttachments[i] = attachment; } // TODO: depth/stencil attachment formats { MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor]; const auto &attributes = key.vertexAttributes; for (const auto &pair : this->attributes) { int i = pair.second; uint32 bit = 1u << i; if (attributes.enableBits & bit) { const auto &attrib = attributes.attribs[i]; int metalBufferIndex = attrib.bufferIndex + VERTEX_BUFFER_BINDING_START; vertdesc.attributes[i].format = getMTLVertexFormat(attrib.format); vertdesc.attributes[i].offset = attrib.offsetFromVertex; vertdesc.attributes[i].bufferIndex = metalBufferIndex; const auto &layout = attributes.bufferLayouts[attrib.bufferIndex]; bool instanced = attributes.instanceBits & (1u << attrib.bufferIndex); auto step = instanced ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex; vertdesc.layouts[metalBufferIndex].stride = layout.stride; vertdesc.layouts[metalBufferIndex].stepFunction = step; } else { vertdesc.attributes[i].format = MTLVertexFormatFloat4; vertdesc.attributes[i].offset = 0; vertdesc.attributes[i].bufferIndex = DEFAULT_VERTEX_BUFFER_BINDING; vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stride = sizeof(float) * 4; vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stepFunction = MTLVertexStepFunctionConstant; vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stepRate = 0; } } desc.vertexDescriptor = vertdesc; } NSError *err = nil; id pipeline = [device newRenderPipelineStateWithDescriptor:desc error:&err]; if (err != nil) { NSLog(@"Error creating render pipeline: %@", err); return nil; } cachedRenderPipelines[key] = CFBridgingRetain(pipeline); return pipeline; } int Shader::getUniformBufferBinding() { return spirv_cross::ResourceBindingPushConstantBinding; } } // metal } // graphics } // love