From c6e4cfdc6440ad18a94ae25f950ae6d45b6428ea Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 24 Dec 2021 23:04:44 -0400 Subject: [PATCH] metal: initial love.graphics.dispatchThreadgroups implementation --- src/modules/graphics/metal/Graphics.h | 1 + src/modules/graphics/metal/Graphics.mm | 120 ++++++++++++++++++++++++- src/modules/graphics/metal/Shader.h | 2 + src/modules/graphics/metal/Shader.mm | 21 +++++ 4 files changed, 142 insertions(+), 2 deletions(-) diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index bd6b39df0..637603553 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -203,6 +203,7 @@ private: id getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil); void applyRenderState(id renderEncoder, const VertexAttributes &attributes); + void applyShaderUniforms(id encoder, Shader *shader); void applyShaderUniforms(id renderEncoder, Shader *shader, Texture *maintex); id commandQueue; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index baaade4f1..44acc9173 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -168,6 +168,23 @@ static inline void setBuffer(id encoder, Graphics::Rend } } +static inline void setBuffer(id encoder, Graphics::RenderEncoderBindings &bindings, int index, id buffer, size_t offset) +{ + void *b = (__bridge void *)buffer; + auto &binding = bindings.buffers[index][SHADERSTAGE_COMPUTE]; + if (binding.buffer != b) + { + binding.buffer = b; + binding.offset = offset; + [encoder setBuffer:buffer offset:offset atIndex:index]; + } + else if (binding.offset != offset) + { + binding.offset = offset; + [encoder setBufferOffset:offset atIndex:index]; + } +} + static inline void setTexture(id encoder, Graphics::RenderEncoderBindings &bindings, ShaderStageType stage, int index, id texture) { void *t = (__bridge void *)texture; @@ -182,6 +199,17 @@ static inline void setTexture(id encoder, Graphics::Ren } } +static inline void setTexture(id encoder, Graphics::RenderEncoderBindings &bindings, int index, id texture) +{ + void *t = (__bridge void *)texture; + auto &binding = bindings.textures[index][SHADERSTAGE_COMPUTE]; + if (binding != t) + { + binding = t; + [encoder setTexture:texture atIndex:index]; + } +} + static inline void setSampler(id encoder, Graphics::RenderEncoderBindings &bindings, ShaderStageType stage, int index, id sampler) { void *s = (__bridge void *)sampler; @@ -196,6 +224,17 @@ static inline void setSampler(id encoder, Graphics::Ren } } +static inline void setSampler(id encoder, Graphics::RenderEncoderBindings &bindings, int index, id sampler) +{ + void *s = (__bridge void *)sampler; + auto &binding = bindings.samplers[index][SHADERSTAGE_COMPUTE]; + if (binding != s) + { + binding = s; + [encoder setSamplerState:sampler atIndex:index]; + } +} + love::graphics::Graphics *createInstance() { love::graphics::Graphics *instance = nullptr; @@ -662,6 +701,7 @@ id Graphics::useComputeEncoder() submitRenderEncoder(SUBMIT_STORE); submitBlitEncoder(); computeEncoder = [useCommandBuffer() computeCommandEncoder]; + renderBindings = {}; } return computeEncoder; @@ -895,6 +935,63 @@ void Graphics::applyRenderState(id encoder, const Verte dirtyRenderState = 0; } +void Graphics::applyShaderUniforms(id encoder, love::graphics::Shader *shader) +{ + Shader *s = (Shader *)shader; + +#ifdef LOVE_MACOS + size_t alignment = 256; +#else + size_t alignment = 16; +#endif + + size_t size = s->getLocalUniformBufferSize(); + uint8 *bufferdata = s->getLocalUniformBufferData(); + + if (uniformBuffer->getSize() < uniformBufferOffset + size) + { + size_t newsize = uniformBuffer->getSize() * 2; + uniformBuffer->release(); + uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, newsize); + uniformBufferData = {}; + uniformBufferOffset = 0; + } + + if (uniformBufferData.data == nullptr) + uniformBufferData = uniformBuffer->map(uniformBuffer->getSize()); + + memcpy(uniformBufferData.data + uniformBufferOffset, bufferdata, size); + + id buffer = getMTLBuffer(uniformBuffer); + int uniformindex = Shader::getUniformBufferBinding(); + + auto &bindings = renderBindings; + setBuffer(encoder, bindings, uniformindex, buffer, uniformBufferOffset); + + uniformBufferOffset += alignUp(size, alignment); + + for (const Shader::TextureBinding &b : s->getTextureBindings()) + { + id texture = b.texture; + id sampler = b.sampler; + + uint8 texindex = b.textureStages[SHADERSTAGE_COMPUTE]; + uint8 sampindex = b.samplerStages[SHADERSTAGE_COMPUTE]; + + if (texindex != LOVE_UINT8_MAX) + setTexture(encoder, bindings, texindex, texture); + if (sampindex != LOVE_UINT8_MAX) + setSampler(encoder, bindings, sampindex, sampler); + } + + for (const Shader::BufferBinding &b : s->getBufferBindings()) + { + uint8 index = b.stages[SHADERSTAGE_COMPUTE]; + if (index != LOVE_UINT8_MAX) + setBuffer(encoder, bindings, index, b.buffer, 0); + } +} + void Graphics::applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, love::graphics::Texture *maintex) { Shader *s = (Shader *)shader; @@ -1178,8 +1275,27 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute bool Graphics::dispatch(int x, int y, int z) { @autoreleasepool { - // TODO - return false; + // Set by higher level code before calling dispatch(x, y, z). + auto shader = (Shader *) Shader::current; + + int tX, tY, tZ; + shader->getLocalThreadgroupSize(&tX, &tY, &tZ); + + id pipeline = shader->getComputePipeline(); + if (pipeline == nil) + return false; + + id computeEncoder = useComputeEncoder(); + + applyShaderUniforms(computeEncoder, shader); + + // TODO: track this state? + [computeEncoder setComputePipelineState:pipeline]; + + [computeEncoder dispatchThreadgroups:MTLSizeMake(x, y, z) + threadsPerThreadgroup:MTLSizeMake(tX, tY, tZ)]; + + return true; }} void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int /*pixelw*/, int /*pixelh*/, bool /*hasSRGBtexture*/) diff --git a/src/modules/graphics/metal/Shader.h b/src/modules/graphics/metal/Shader.h index 4df8a372d..939b68482 100644 --- a/src/modules/graphics/metal/Shader.h +++ b/src/modules/graphics/metal/Shader.h @@ -107,6 +107,7 @@ public: void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override; id getCachedRenderPipeline(const RenderPipelineKey &key); + id getComputePipeline() const { return computePipeline; } static int getUniformBufferBinding(); const std::vector &getTextureBindings() const { return textureBindings; } @@ -144,6 +145,7 @@ private: std::vector bufferBindings; std::unordered_map cachedRenderPipelines; + id computePipeline; }; // Metal diff --git a/src/modules/graphics/metal/Shader.mm b/src/modules/graphics/metal/Shader.mm index f97f74ec9..7387ebf15 100644 --- a/src/modules/graphics/metal/Shader.mm +++ b/src/modules/graphics/metal/Shader.mm @@ -347,6 +347,27 @@ Shader::Shader(id device, StrongRef stag } cleanup(); + + if (functions[SHADERSTAGE_COMPUTE] != nil) + { + MTLComputePipelineDescriptor *desc = [MTLComputePipelineDescriptor new]; + desc.computeFunction = functions[SHADERSTAGE_COMPUTE]; + + // TODO: threadGroupSizeIsMultipleOfThreadExecutionWidth + + NSError *err = nil; + computePipeline = [device newComputePipelineStateWithDescriptor:desc + options:MTLPipelineOptionNone + reflection:nil + error:&err]; + if (computePipeline == nil) + { + if (err != nil) + throw love::Exception("Error creating compute shader pipeline: %s", err.localizedDescription.UTF8String); + else + throw love::Exception("Error creating compute shader pipeline."); + } + } }} void Shader::compileFromGLSLang(id device, const glslang::TProgram &program)