Browse Source

metal: avoid redundant resource binding calls.

Alex Szpakowski 4 years ago
parent
commit
8c88d645a3

+ 13 - 11
src/modules/graphics/metal/Graphics.h

@@ -39,6 +39,17 @@ class Graphics final : public love::graphics::Graphics
 {
 public:
 
+	struct RenderEncoderBindings
+	{
+		void *textures[32][ShaderStage::STAGE_MAX_ENUM];
+		void *samplers[32][ShaderStage::STAGE_MAX_ENUM];
+		struct
+		{
+			void *buffer;
+			size_t offset;
+		} buffers[32][ShaderStage::STAGE_MAX_ENUM];
+	};
+
 	Graphics();
 	virtual ~Graphics();
 
@@ -153,17 +164,6 @@ private:
 		STATEBIT_ALL = 0xFFFFFFFF
 	};
 
-	struct RenderState
-	{
-		Rect viewport = {0, 0, 0, 0};
-		ScissorState scissor;
-		BlendState blend;
-		DepthState depth;
-		StencilState stencil;
-		ColorChannelMask colorChannelMask;
-		Shader *shader;
-	};
-
 	struct DeviceFamilies
 	{
 		// All arrays are 1-indexed for convenience
@@ -213,6 +213,8 @@ private:
 
 	AttachmentStoreActions attachmentStoreActions;
 
+	RenderEncoderBindings renderBindings;
+
 	StreamBuffer *uniformBuffer;
 	StreamBuffer::MapInfo uniformBufferData;
 	size_t uniformBufferOffset;

+ 109 - 50
src/modules/graphics/metal/Graphics.mm

@@ -145,6 +145,57 @@ static inline id<MTLBuffer> getMTLBuffer(love::graphics::Resource *res)
 	return res ? (__bridge id<MTLBuffer>)(void *) res->getHandle() : nil;
 }
 
+static inline void setBuffer(id<MTLRenderCommandEncoder> encoder, Graphics::RenderEncoderBindings &bindings, ShaderStage::StageType stage, int index, id<MTLBuffer> buffer, size_t offset)
+{
+	void *b = (__bridge void *)buffer;
+	auto &binding = bindings.buffers[index][stage];
+	if (binding.buffer != b)
+	{
+		binding.buffer = b;
+		binding.offset = offset;
+		if (stage == ShaderStage::STAGE_VERTEX)
+			[encoder setVertexBuffer:buffer offset:offset atIndex:index];
+		else if (stage == ShaderStage::STAGE_PIXEL)
+			[encoder setFragmentBuffer:buffer offset:offset atIndex:index];
+	}
+	else if (binding.offset != offset)
+	{
+		binding.offset = offset;
+		if (stage == ShaderStage::STAGE_VERTEX)
+			[encoder setVertexBufferOffset:offset atIndex:index];
+		else if (stage == ShaderStage::STAGE_PIXEL)
+			[encoder setFragmentBufferOffset:offset atIndex:index];
+	}
+}
+
+static inline void setTexture(id<MTLRenderCommandEncoder> encoder, Graphics::RenderEncoderBindings &bindings, ShaderStage::StageType stage, int index, id<MTLTexture> texture)
+{
+	void *t = (__bridge void *)texture;
+	auto &binding = bindings.textures[index][stage];
+	if (binding != t)
+	{
+		binding = t;
+		if (stage == ShaderStage::STAGE_VERTEX)
+			[encoder setVertexTexture:texture atIndex:index];
+		else if (stage == ShaderStage::STAGE_PIXEL)
+			[encoder setFragmentTexture:texture atIndex:index];
+	}
+}
+
+static inline void setSampler(id<MTLRenderCommandEncoder> encoder, Graphics::RenderEncoderBindings &bindings, ShaderStage::StageType stage, int index, id<MTLSamplerState> sampler)
+{
+	void *s = (__bridge void *)sampler;
+	auto &binding = bindings.samplers[index][stage];
+	if (binding != s)
+	{
+		binding = s;
+		if (stage == ShaderStage::STAGE_VERTEX)
+			[encoder setVertexSamplerState:sampler atIndex:index];
+		else if (stage == ShaderStage::STAGE_PIXEL)
+			[encoder setFragmentSamplerState:sampler atIndex:index];
+	}
+}
+
 love::graphics::Graphics *createInstance()
 {
 	love::graphics::Graphics *instance = nullptr;
@@ -182,6 +233,7 @@ Graphics::Graphics()
 	, windowHasStencil(false)
 	, requestedBackbufferMSAA(0)
 	, attachmentStoreActions()
+	, renderBindings()
 	, uniformBufferOffset(0)
 	, defaultAttributesBuffer(nullptr)
 	, defaultTextures()
@@ -226,6 +278,35 @@ Graphics::Graphics()
 		defaultTextures[i]->replacePixels(defaultpixel, sizeof(defaultpixel), 0, 0, r, false);
 	}
 
+	if (batchedDrawState.vb[0] == nullptr)
+	{
+		// Initial sizes that should be good enough for most cases. It will
+		// resize to fit if needed, later.
+		batchedDrawState.vb[0] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawState.vb[1] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 256  * 1024 * 1);
+		batchedDrawState.indexBuffer = CreateStreamBuffer(device, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+	}
+
+	createQuadIndexBuffer();
+
+	// We always need a default shader.
+	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
+	{
+		auto stype = (Shader::StandardShader) i;
+		if (!Shader::standardShaders[i])
+		{
+			std::vector<std::string> stages;
+			stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_VERTEX));
+			stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_PIXEL));
+			Shader::standardShaders[i] = newShader(stages);
+		}
+	}
+
+	// A shader should always be active, but the default shader shouldn't be
+	// returned by getShader(), so we don't do setShader(defaultShader).
+	if (!Shader::current)
+		Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
+
 	auto window = Module::getInstance<love::window::Window>(M_WINDOW);
 
 	if (window != nullptr)
@@ -347,38 +428,9 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 
 	created = true;
 
-	if (batchedDrawState.vb[0] == nullptr)
-	{
-		// Initial sizes that should be good enough for most cases. It will
-		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 256  * 1024 * 1);
-		batchedDrawState.indexBuffer = CreateStreamBuffer(device, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
-	}
-
-	createQuadIndexBuffer();
-
 	// Restore the graphics state.
 	restoreState(states.back());
 
-	// We always need a default shader.
-	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
-	{
-		auto stype = (Shader::StandardShader) i;
-		if (!Shader::standardShaders[i])
-		{
-			std::vector<std::string> stages;
-			stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_VERTEX));
-			stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_PIXEL));
-			Shader::standardShaders[i] = newShader(stages);
-		}
-	}
-
-	// A shader should always be active, but the default shader shouldn't be
-	// returned by getShader(), so we don't do setShader(defaultShader).
-	if (!Shader::current)
-		Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
-
 	return true;
 }}
 
@@ -441,7 +493,7 @@ void Graphics::submitCommandBuffer()
 	}
 }
 
-static inline void setAttachment(const Graphics::RenderTarget &rt, MTLRenderPassAttachmentDescriptor *desc, MTLStoreAction &storeaction)
+static inline void setAttachment(const Graphics::RenderTarget &rt, MTLRenderPassAttachmentDescriptor *desc, MTLStoreAction &storeaction, bool setload = true)
 {
 	bool isvolume = rt.texture->getTextureType() == TEXTURE_VOLUME;
 
@@ -450,8 +502,12 @@ static inline void setAttachment(const Graphics::RenderTarget &rt, MTLRenderPass
 	desc.slice = isvolume ? 0 : rt.slice;
 	desc.depthPlane = isvolume ? rt.slice : 0;
 
-	// Default to load until clear or discard is called.
-	desc.loadAction = MTLLoadActionLoad;
+	if (setload)
+	{
+		// Default to load until clear or discard is called.
+		desc.loadAction = MTLLoadActionLoad;
+	}
+
 	desc.storeAction = MTLStoreActionUnknown;
 	storeaction = MTLStoreActionStore;
 
@@ -504,14 +560,16 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 			passDesc.colorAttachments[0].depthPlane = 0;
 
 			RenderTarget rt(backbufferDepthStencil);
-			setAttachment(rt, passDesc.depthAttachment, attachmentStoreActions.depth);
-			setAttachment(rt, passDesc.stencilAttachment, attachmentStoreActions.stencil);
+			setAttachment(rt, passDesc.depthAttachment, attachmentStoreActions.depth, false);
+			setAttachment(rt, passDesc.stencilAttachment, attachmentStoreActions.stencil, false);
 			attachmentStoreActions.depth = MTLStoreActionDontCare;
 			attachmentStoreActions.stencil = MTLStoreActionDontCare;
 		}
 
 		renderEncoder = [useCommandBuffer() renderCommandEncoderWithDescriptor:passDesc];
 
+		renderBindings = {};
+
 		for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
 		{
 			passDesc.colorAttachments[0].texture = nil;
@@ -524,7 +582,7 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 		passDesc.stencilAttachment.resolveTexture = nil;
 
 		id<MTLBuffer> defaultbuffer = getMTLBuffer(defaultAttributesBuffer);
-		[renderEncoder setVertexBuffer:defaultbuffer offset:0 atIndex:DEFAULT_VERTEX_BUFFER_BINDING];
+		setBuffer(renderEncoder, renderBindings, ShaderStage::STAGE_VERTEX, DEFAULT_VERTEX_BUFFER_BINDING, defaultbuffer, 0);
 
 		dirtyRenderState = STATEBIT_ALL;
 	}
@@ -850,8 +908,9 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
 	id<MTLBuffer> buffer = getMTLBuffer(uniformBuffer);
 	int uniformindex = Shader::getUniformBufferBinding();
 
-	[renderEncoder setVertexBuffer:buffer offset:uniformBufferOffset atIndex:uniformindex];
-	[renderEncoder setFragmentBuffer:buffer offset:uniformBufferOffset atIndex:uniformindex];
+	auto &bindings = renderBindings;
+	setBuffer(renderEncoder, bindings, ShaderStage::STAGE_VERTEX, uniformindex, buffer, uniformBufferOffset);
+	setBuffer(renderEncoder, bindings, ShaderStage::STAGE_PIXEL, uniformindex, buffer, uniformBufferOffset);
 
 	uniformBufferOffset += alignUp(size, alignment);
 
@@ -873,25 +932,25 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
 			sampler = getMTLSampler(maintex);
 		}
 
-		uint8 texindex = b.texturestages[ShaderStage::STAGE_VERTEX];
-		uint8 sampindex = b.samplerstages[ShaderStage::STAGE_VERTEX];
+		uint8 texindex = b.textureStages[ShaderStage::STAGE_VERTEX];
+		uint8 sampindex = b.samplerStages[ShaderStage::STAGE_VERTEX];
 
 		if (texindex != LOVE_UINT8_MAX)
-			[renderEncoder setVertexTexture:texture atIndex:texindex];
+			setTexture(renderEncoder, bindings, ShaderStage::STAGE_VERTEX, texindex, texture);
 		if (sampindex != LOVE_UINT8_MAX)
-			[renderEncoder setVertexSamplerState:sampler atIndex:sampindex];
+			setSampler(renderEncoder, bindings, ShaderStage::STAGE_VERTEX, sampindex, sampler);
 
-		texindex = b.texturestages[ShaderStage::STAGE_PIXEL];
-		sampindex = b.samplerstages[ShaderStage::STAGE_PIXEL];
+		texindex = b.textureStages[ShaderStage::STAGE_PIXEL];
+		sampindex = b.samplerStages[ShaderStage::STAGE_PIXEL];
 
 		if (texindex != LOVE_UINT8_MAX)
-			[renderEncoder setFragmentTexture:texture atIndex:texindex];
+			setTexture(renderEncoder, bindings, ShaderStage::STAGE_PIXEL, texindex, texture);
 		if (sampindex != LOVE_UINT8_MAX)
-			[renderEncoder setFragmentSamplerState:sampler atIndex:sampindex];
+			setSampler(renderEncoder, bindings, ShaderStage::STAGE_PIXEL, sampindex, sampler);
 	}
 }
 
-static void setVertexBuffers(id<MTLRenderCommandEncoder> encoder, const BufferBindings *buffers)
+static void setVertexBuffers(id<MTLRenderCommandEncoder> encoder, const BufferBindings *buffers, Graphics::RenderEncoderBindings &bindings)
 {
 	uint32 allbits = buffers->useBits;
 	uint32 i = 0;
@@ -903,7 +962,7 @@ static void setVertexBuffers(id<MTLRenderCommandEncoder> encoder, const BufferBi
 		{
 			auto b = buffers->info[i];
 			id<MTLBuffer> buffer = getMTLBuffer(b.buffer);
-			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i + VERTEX_BUFFER_BINDING_START];
+			setBuffer(encoder, bindings, ShaderStage::STAGE_VERTEX, i + VERTEX_BUFFER_BINDING_START, buffer, b.offset);
 		}
 
 		i++;
@@ -920,7 +979,7 @@ void Graphics::draw(const DrawCommand &cmd)
 
 	[encoder setCullMode:MTLCullModeNone];
 
-	setVertexBuffers(encoder, cmd.buffers);
+	setVertexBuffers(encoder, cmd.buffers, renderBindings);
 
 	[encoder drawPrimitives:getMTLPrimitiveType(cmd.primitiveType)
 				vertexStart:cmd.vertexStart
@@ -937,7 +996,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 
 	[encoder setCullMode:MTLCullModeNone];
 
-	setVertexBuffers(encoder, cmd.buffers);
+	setVertexBuffers(encoder, cmd.buffers, renderBindings);
 
 	auto indexType = cmd.indexType == INDEX_UINT32 ? MTLIndexTypeUInt32 : MTLIndexTypeUInt16;
 
@@ -961,7 +1020,7 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute
 
 	[encoder setCullMode:MTLCullModeNone];
 
-	setVertexBuffers(encoder, &buffers);
+	setVertexBuffers(encoder, &buffers, renderBindings);
 
 	id<MTLBuffer> ib = getMTLBuffer(quadIndexBuffer);
 

+ 0 - 2
src/modules/graphics/metal/Metal.h

@@ -47,8 +47,6 @@ public:
 
 }; // Metal
 
-extern Metal metal;
-
 } // metal
 } // graphics
 } // love

+ 0 - 2
src/modules/graphics/metal/Metal.mm

@@ -346,8 +346,6 @@ Metal::PixelFormatDesc Metal::convertPixelFormat(PixelFormat format, bool &isSRG
 	return desc;
 }
 
-Metal metal;
-
 } // metal
 } // graphics
 } // love

+ 2 - 2
src/modules/graphics/metal/Shader.h

@@ -80,8 +80,8 @@ public:
 
 		bool isMainTexture;
 
-		uint8 texturestages[ShaderStage::STAGE_MAX_ENUM];
-		uint8 samplerstages[ShaderStage::STAGE_MAX_ENUM];
+		uint8 textureStages[ShaderStage::STAGE_MAX_ENUM];
+		uint8 samplerStages[ShaderStage::STAGE_MAX_ENUM];
 	};
 
 	struct BufferBinding

+ 4 - 4
src/modules/graphics/metal/Shader.mm

@@ -672,17 +672,17 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 								b.isMainTexture = true;
 						}
 
-						for (uint8 &stagebinding : b.texturestages)
+						for (uint8 &stagebinding : b.textureStages)
 							stagebinding = LOVE_UINT8_MAX;
-						for (uint8 &stagebinding : b.samplerstages)
+						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;
+					b.textureStages[stageindex] = (uint8) texturebinding;
+					b.samplerStages[stageindex] = (uint8) samplerbinding;
 				}
 			}
 		}