From 30fe00d767825c913c80bdf709c5b51cf3afb1da Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sat, 25 Dec 2021 14:21:45 -0400 Subject: [PATCH] metal: implement storage buffer bindings --- src/modules/graphics/metal/Graphics.mm | 36 +++++-- src/modules/graphics/metal/Shader.h | 5 +- src/modules/graphics/metal/Shader.mm | 133 ++++++++++++++++++------- 3 files changed, 124 insertions(+), 50 deletions(-) diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 44acc9173..889d1d085 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -742,7 +742,7 @@ id Graphics::getCachedSampler(const SamplerState &s) if (isClampOne(s.wrapU) || isClampOne(s.wrapV) || isClampOne(s.wrapW)) { - if (@available(macOS 10.12, iOS 10.14, *)) + if (@available(macOS 10.12, iOS 14.0, *)) desc.borderColor = MTLSamplerBorderColorOpaqueWhite; } @@ -1110,8 +1110,10 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo } } -static void setVertexBuffers(id encoder, const BufferBindings *buffers, Graphics::RenderEncoderBindings &bindings) +static void setVertexBuffers(id encoder, love::graphics::Shader *shader, const BufferBindings *buffers, Graphics::RenderEncoderBindings &bindings) { + Shader *s = (Shader *)shader; + int firstBinding = s->getFirstVertexBufferBinding(); uint32 allbits = buffers->useBits; uint32 i = 0; while (allbits) @@ -1122,7 +1124,7 @@ static void setVertexBuffers(id encoder, const BufferBi { auto b = buffers->info[i]; id buffer = getMTLBuffer(b.buffer); - setBuffer(encoder, bindings, SHADERSTAGE_VERTEX, i + VERTEX_BUFFER_BINDING_START, buffer, b.offset); + setBuffer(encoder, bindings, SHADERSTAGE_VERTEX, firstBinding + i, buffer, b.offset); } i++; @@ -1143,7 +1145,7 @@ void Graphics::draw(const DrawCommand &cmd) applyRenderState(encoder, *cmd.attributes); applyShaderUniforms(encoder, Shader::current, cmd.texture); - setVertexBuffers(encoder, cmd.buffers, renderBindings); + setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); [encoder drawPrimitives:getMTLPrimitiveType(cmd.primitiveType) vertexStart:cmd.vertexStart @@ -1166,9 +1168,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd) applyRenderState(encoder, *cmd.attributes); applyShaderUniforms(encoder, Shader::current, cmd.texture); - [encoder setCullMode:MTLCullModeNone]; - - setVertexBuffers(encoder, cmd.buffers, renderBindings); + setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); auto indexType = cmd.indexType == INDEX_UINT32 ? MTLIndexTypeUInt32 : MTLIndexTypeUInt16; @@ -1226,7 +1226,7 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute // Some older iOS devices don't support base vertex rendering. if (families.apple[3] || families.mac[1] || families.macCatalyst[1]) { - setVertexBuffers(encoder, &buffers, renderBindings); + setVertexBuffers(encoder, Shader::current, &buffers, renderBindings); int basevertex = start * 4; @@ -1255,7 +1255,7 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW) { - setVertexBuffers(encoder, &bufferscopy, renderBindings); + setVertexBuffers(encoder, Shader::current, &bufferscopy, renderBindings); int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex); @@ -1791,6 +1791,7 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags format = getSRGBPixelFormat(format); const uint32 sample = PIXELFORMATUSAGEFLAGS_SAMPLE; + const uint32 linear = PIXELFORMATUSAGEFLAGS_LINEAR; const uint32 rt = PIXELFORMATUSAGEFLAGS_RENDERTARGET; const uint32 blend = PIXELFORMATUSAGEFLAGS_BLEND; const uint32 msaa = PIXELFORMATUSAGEFLAGS_MSAA; @@ -1827,7 +1828,7 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags case PIXELFORMAT_R32_FLOAT: if (families.apple[1]) flags |= sample | rt | blend | msaa | computewrite; - if (families.mac[1]) + if (families.mac[1] || families.macCatalyst[1]) flags |= all; break; @@ -2114,11 +2115,24 @@ void Graphics::initCapabilities() capabilities.limits[LIMIT_TEXTURE_SIZE] = 8192; capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = 8192; } + + // TODO: metal doesn't have a good API to query this? capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = 128 * 1024 * 1024; - capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = 128 * 1024 * 1024; // TODO; + + if (@available(macOS 10.14, iOS 12.0, *)) + { + NSUInteger buffersize = [device maxBufferLength]; + capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = buffersize; + } + else + { + capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = 128 * 1024 * 1024; + } + capabilities.limits[LIMIT_THREADGROUPS_X] = LOVE_INT32_MAX; // TODO: is there a real limit? capabilities.limits[LIMIT_THREADGROUPS_Y] = LOVE_INT32_MAX; capabilities.limits[LIMIT_THREADGROUPS_Z] = LOVE_INT32_MAX; + if (families.mac[1] || families.macCatalyst[1] || families.apple[2]) capabilities.limits[LIMIT_RENDER_TARGETS] = 8; else diff --git a/src/modules/graphics/metal/Shader.h b/src/modules/graphics/metal/Shader.h index 939b68482..069c56de4 100644 --- a/src/modules/graphics/metal/Shader.h +++ b/src/modules/graphics/metal/Shader.h @@ -47,7 +47,6 @@ namespace metal { static const int DEFAULT_VERTEX_BUFFER_BINDING = 1; -static const int VERTEX_BUFFER_BINDING_START = 2; class Shader final : public love::graphics::Shader { @@ -110,6 +109,8 @@ public: id getComputePipeline() const { return computePipeline; } static int getUniformBufferBinding(); + int getFirstVertexBufferBinding() const { return firstVertexBufferBinding; } + const std::vector &getTextureBindings() const { return textureBindings; } const std::vector &getBufferBindings() const { return bufferBindings; } @@ -139,6 +140,8 @@ private: size_t localUniformBufferSize; size_t builtinUniformDataOffset; + int firstVertexBufferBinding; + std::map attributes; std::vector textureBindings; diff --git a/src/modules/graphics/metal/Shader.mm b/src/modules/graphics/metal/Shader.mm index 7387ebf15..1564b7329 100644 --- a/src/modules/graphics/metal/Shader.mm +++ b/src/modules/graphics/metal/Shader.mm @@ -237,6 +237,11 @@ static inline id getMTLSampler(love::graphics::Texture *tex) return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; } +static inline id getMTLBuffer(love::graphics::Buffer *buffer) +{ + return buffer ? (__bridge id)(void *) buffer->getHandle() : nil; +} + static EShLanguage getGLSLangStage(ShaderStageType stage) { switch (stage) @@ -257,6 +262,7 @@ Shader::Shader(id device, StrongRef stag , localUniformBufferData(nullptr) , localUniformBufferSize(0) , builtinUniformDataOffset(0) + , firstVertexBufferBinding(DEFAULT_VERTEX_BUFFER_BINDING + 1) { @autoreleasepool { using namespace glslang; @@ -380,6 +386,11 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p std::map varyings; int nextVaryingLocation = 0; + int metalBufferIndices[SHADERSTAGE_MAX_ENUM]; + for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) + metalBufferIndices[i] = getUniformBufferBinding() + 1; + metalBufferIndices[SHADERSTAGE_VERTEX] = DEFAULT_VERTEX_BUFFER_BINDING + 1; + for (int stageindex = 0; stageindex < SHADERSTAGE_MAX_ENUM; stageindex++) { auto glslangstage = getGLSLangStage((ShaderStageType) stageindex); @@ -420,6 +431,7 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p u.name = resource.name; u.count = type.array.empty() ? 1 : type.array[0]; u.isDepthSampler = type.image.depth; + u.components = 1; switch (imagetype.basetype) { @@ -461,7 +473,8 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p break; } - u.data = malloc(sizeof(int) * u.count); + u.dataSize = sizeof(int) * u.count; + u.data = malloc(u.dataSize); for (int i = 0; i < u.count; i++) u.ints[i] = -1; // Initialized below, after compiling. @@ -489,12 +502,13 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p for (const auto &resource : resources.uniform_buffers) { + 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); + 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); @@ -527,6 +541,7 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p u.name = msl.get_member_name(type.self, uindex); u.dataSize = membersize; u.count = membertype.array.empty() ? 1 : membertype.array[0]; + u.components = 1; switch (membertype.basetype) { @@ -568,19 +583,60 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p builtinUniformInfo[builtin] = &uniforms[u.name]; } } - + } + else + { + binding.msl_buffer = metalBufferIndices[stageindex]++; + msl.add_msl_resource_binding(binding); } } for (const auto &resource : resources.storage_buffers) { + 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 = metalBufferIndices[stageindex]++; + msl.add_msl_resource_binding(binding); + auto it = uniforms.find(resource.name); if (it != uniforms.end()) + continue; + + const SPIRType &type = msl.get_type(resource.type_id); + + UniformInfo u = {}; + u.baseType = UNIFORM_STORAGEBUFFER; + u.components = 1; + u.name = resource.name; + u.count = type.array.empty() ? 1 : type.array[0]; + + const auto reflectionit = validationReflection.storageBuffers.find(u.name); + if (reflectionit != validationReflection.storageBuffers.end()) { + u.bufferStride = reflectionit->second.stride; + u.bufferMemberCount = reflectionit->second.memberCount; + u.access = reflectionit->second.access; + } + else + { + // No reflection info - maybe glslang was better at detecting + // dead code than the driver's compiler? continue; } - // TODO + u.buffers = new love::graphics::Buffer*[u.count]; + u.dataSize = sizeof(int) * u.count; + u.data = malloc(u.dataSize); + + for (int i = 0; i < u.count; i++) + { + u.ints[i] = -1; // Initialized below, after compiling. + u.buffers[i] = nullptr; // TODO + } + + uniforms[u.name] = u; } if (stageindex == SHADERSTAGE_VERTEX) @@ -741,6 +797,8 @@ void Shader::compileFromGLSLang(id device, const glslang::TProgram &p printf("Error parsing SPIR-V shader source: %s\n", e.what()); } } + + firstVertexBufferBinding = metalBufferIndices[SHADERSTAGE_VERTEX]; } Shader::~Shader() @@ -896,6 +954,7 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe else if (storagebinding) { // TODO + bufferBindings[info->ints[i]].buffer = getMTLBuffer(buffer); } } } @@ -947,7 +1006,7 @@ id Shader::getCachedRenderPipeline(const RenderPipelineK MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i]; - if (@available(macOS 10.15, iOS 13, *)) + if (@available(macOS 10.15, iOS 13.0, *)) { // We already don't really support metal on older systems, this just // silences a compiler warning about it. @@ -998,47 +1057,45 @@ id Shader::getCachedRenderPipeline(const RenderPipelineK } } + MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor]; + const auto &attributes = key.vertexAttributes; + + for (const auto &pair : this->attributes) { - MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor]; - const auto &attributes = key.vertexAttributes; + int i = pair.second; + uint32 bit = 1u << i; - for (const auto &pair : this->attributes) + if (attributes.enableBits & bit) { - int i = pair.second; - uint32 bit = 1u << i; + const auto &attrib = attributes.attribs[i]; + int metalBufferIndex = firstVertexBufferBinding + attrib.bufferIndex; - 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; - 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]; - const auto &layout = attributes.bufferLayouts[attrib.bufferIndex]; + bool instanced = attributes.instanceBits & (1u << attrib.bufferIndex); + auto step = instanced ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex; - 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; - } + 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; - desc.vertexDescriptor = vertdesc; + 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];