Browse Source

metal: more robust render pass handling.

Initial implementation of captureScreenshot.
Fix getStats().shaderswitches.
Fix getStats().drawcalls.
Alex Szpakowski 4 years ago
parent
commit
2ec9f88f7a

+ 10 - 3
src/modules/graphics/metal/Graphics.h

@@ -39,6 +39,12 @@ class Graphics final : public love::graphics::Graphics
 {
 public:
 
+	enum SubmitType
+	{
+		SUBMIT_DONE,
+		SUBMIT_STORE,
+	};
+
 	struct RenderEncoderBindings
 	{
 		void *textures[32][ShaderStage::STAGE_MAX_ENUM];
@@ -107,15 +113,15 @@ public:
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 
-	void attachShader(love::graphics::Shader *shader);
+	void setShaderChanged();
 
 	id<MTLCommandBuffer> useCommandBuffer();
 	id<MTLCommandBuffer> getCommandBuffer() const { return commandBuffer; }
-	void submitCommandBuffer();
+	void submitCommandBuffer(SubmitType type);
 
 	id<MTLRenderCommandEncoder> useRenderEncoder();
 	id<MTLRenderCommandEncoder> getRenderEncoder() const { return renderEncoder; }
-	void submitRenderEncoder();
+	void submitRenderEncoder(SubmitType type);
 
 	id<MTLBlitCommandEncoder> useBlitEncoder();
 	id<MTLBlitCommandEncoder> getBlitEncoder() const { return blitEncoder; }
@@ -206,6 +212,7 @@ private:
 	uint32 dirtyRenderState;
 	VertexAttributes lastVertexAttributes;
 	bool windowHasStencil;
+	int shaderSwitches;
 
 	StrongRef<love::graphics::Texture> backbufferMSAA;
 	StrongRef<love::graphics::Texture> backbufferDepthStencil;

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

@@ -231,6 +231,7 @@ Graphics::Graphics()
 	, passDesc(nil)
 	, dirtyRenderState(STATEBIT_ALL)
 	, windowHasStencil(false)
+	, shaderSwitches(0)
 	, requestedBackbufferMSAA(0)
 	, attachmentStoreActions()
 	, renderBindings()
@@ -331,7 +332,7 @@ Graphics::Graphics()
 
 Graphics::~Graphics()
 { @autoreleasepool {
-	submitCommandBuffer();
+	submitCommandBuffer(SUBMIT_DONE);
 	delete uniformBuffer;
 	delete defaultAttributesBuffer;
 	passDesc = nil;
@@ -441,7 +442,7 @@ void Graphics::unSetMode()
 
 	flushBatchedDraws();
 
-	submitCommandBuffer();
+	submitCommandBuffer(SUBMIT_DONE);
 
 	for (auto temp : temporaryTextures)
 		temp.texture->release();
@@ -459,9 +460,10 @@ void Graphics::setActive(bool enable)
 	active = enable;
 }
 
-void Graphics::attachShader(love::graphics::Shader *shader)
+void Graphics::setShaderChanged()
 {
 	dirtyRenderState |= STATE_SHADER;
+	++shaderSwitches;
 }
 
 id<MTLCommandBuffer> Graphics::useCommandBuffer()
@@ -481,9 +483,9 @@ id<MTLCommandBuffer> Graphics::useCommandBuffer()
 	return commandBuffer;
 }
 
-void Graphics::submitCommandBuffer()
+void Graphics::submitCommandBuffer(SubmitType type)
 {
-	submitRenderEncoder();
+	submitRenderEncoder(type);
 	submitBlitEncoder();
 
 	if (commandBuffer != nil)
@@ -570,17 +572,6 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 
 		renderBindings = {};
 
-		for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
-		{
-			passDesc.colorAttachments[0].texture = nil;
-			passDesc.colorAttachments[0].resolveTexture = nil;
-		}
-
-		passDesc.depthAttachment.texture = nil;
-		passDesc.depthAttachment.resolveTexture = nil;
-		passDesc.stencilAttachment.texture = nil;
-		passDesc.stencilAttachment.resolveTexture = nil;
-
 		id<MTLBuffer> defaultbuffer = getMTLBuffer(defaultAttributesBuffer);
 		setBuffer(renderEncoder, renderBindings, ShaderStage::STAGE_VERTEX, DEFAULT_VERTEX_BUFFER_BINDING, defaultbuffer, 0);
 
@@ -590,28 +581,45 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 	return renderEncoder;
 }
 
-void Graphics::submitRenderEncoder()
+void Graphics::submitRenderEncoder(SubmitType type)
 {
 	if (renderEncoder != nil)
 	{
+		bool store = type == SUBMIT_STORE;
 		const auto &actions = attachmentStoreActions;
 		const auto &rts = states.back().renderTargets;
 		bool isbackbuffer = rts.getFirstTarget().texture.get() == nullptr;
 
 		if (isbackbuffer)
-			[renderEncoder setColorStoreAction:actions.color[0] atIndex:0];
+			[renderEncoder setColorStoreAction:(store ? MTLStoreActionStore : actions.color[0]) atIndex:0];
 
 		for (size_t i = 0; i < rts.colors.size(); i++)
-			[renderEncoder setColorStoreAction:actions.color[i] atIndex:i];
+			[renderEncoder setColorStoreAction:(store ? MTLStoreActionStore : actions.color[i]) atIndex:i];
 
 		if (rts.depthStencil.texture.get() || rts.temporaryRTFlags != 0 || isbackbuffer)
 		{
-			[renderEncoder setDepthStoreAction:actions.depth];
-			[renderEncoder setStencilStoreAction:actions.stencil];
+			[renderEncoder setDepthStoreAction:store ? MTLStoreActionStore : actions.depth];
+			[renderEncoder setStencilStoreAction:store ? MTLStoreActionStore : actions.stencil];
 		}
 
 		[renderEncoder endEncoding];
 		renderEncoder = nil;
+
+		// Reset actions to load. The next clear/discard/etc will set more
+		// appropriate actions if necessary.
+		for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
+		{
+			passDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
+			passDesc.colorAttachments[i].texture = nil;
+			passDesc.colorAttachments[i].resolveTexture = nil;
+		}
+
+		passDesc.depthAttachment.loadAction = MTLLoadActionLoad;
+		passDesc.depthAttachment.texture = nil;
+		passDesc.depthAttachment.resolveTexture = nil;
+		passDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
+		passDesc.stencilAttachment.texture = nil;
+		passDesc.stencilAttachment.resolveTexture = nil;
 	}
 }
 
@@ -619,7 +627,7 @@ id<MTLBlitCommandEncoder> Graphics::useBlitEncoder()
 {
 	if (blitEncoder == nil)
 	{
-		submitRenderEncoder();
+		submitRenderEncoder(SUBMIT_STORE);
 		blitEncoder = [useCommandBuffer() blitCommandEncoder];
 	}
 
@@ -985,6 +993,8 @@ void Graphics::draw(const DrawCommand &cmd)
 				vertexStart:cmd.vertexStart
 				vertexCount:cmd.vertexCount
 			  instanceCount:cmd.instanceCount];
+
+	++drawCalls;
 }}
 
 void Graphics::draw(const DrawIndexedCommand &cmd)
@@ -1006,6 +1016,8 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 					   indexBuffer:getMTLBuffer(cmd.indexBuffer)
 				 indexBufferOffset:cmd.indexBufferOffset
 					 instanceCount:cmd.instanceCount];
+
+	++drawCalls;
 }}
 
 void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture)
@@ -1099,13 +1111,14 @@ void Graphics::endPass()
 	auto &rts = states.back().renderTargets;
 	love::graphics::Texture *depthstencil = rts.depthStencil.texture.get();
 
-	// Discard the depth/stencil buffer if we're using an internal cached one.
+	// Discard the depth/stencil buffer if we're using an internal cached one,
+	// or if this is the backbuffer.
 	if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0)
 		discard({}, true);
 	else if (!rts.getFirstTarget().texture.get())
 		discard({}, true); // Backbuffer
 
-	submitRenderEncoder();
+	submitRenderEncoder(SUBMIT_DONE);
 
 	for (const auto &rt : rts.colors)
 	{
@@ -1117,9 +1130,17 @@ void Graphics::endPass()
 void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth)
 { @autoreleasepool {
 	if (c.hasValue || stencil.hasValue || depth.hasValue)
+	{
 		flushBatchedDraws();
 
-	// TODO: handle clearing mid-pass
+		// Handle clearing mid-pass by starting a new pass.
+		if (renderEncoder != nil)
+		{
+			submitRenderEncoder(SUBMIT_STORE);
+			useRenderEncoder();
+		}
+	}
+
 	if (c.hasValue)
 	{
 		gammaCorrectColor(c.value);
@@ -1160,7 +1181,13 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors, OptionalInt sten
 
 	flushBatchedDraws();
 
-	// TODO: handle clearing mid-pass
+	// Handle clearing mid-pass by starting a new pass.
+	if (renderEncoder != nil)
+	{
+		submitRenderEncoder(SUBMIT_STORE);
+		useRenderEncoder();
+	}
+
 	for (int i = 0; i < ncolors; i++)
 	{
 		if (!colors[i].hasValue)
@@ -1204,31 +1231,55 @@ void Graphics::present(void *screenshotCallbackData)
 
 	endPass();
 
+	id<MTLBuffer> screenshotbuffer = nil;
+
 	if (!pendingScreenshotCallbacks.empty())
 	{
-		int w = getPixelWidth();
-		int h = getPixelHeight();
+		int w = activeDrawable.texture.width;
+		int h = activeDrawable.texture.height;
+		size_t size = w * h * 4;
 
-		size_t row = 4 * w;
-		size_t size = row * h;
+		screenshotbuffer = [device newBufferWithLength:size options:MTLResourceStorageModeShared];
+		if (screenshotbuffer == nil)
+			throw love::Exception("Out of graphics memory.");
 
-		uint8 *screenshot = nullptr;
+		auto blitencoder = useBlitEncoder();
 
-		try
-		{
-			screenshot = new uint8[size];
-		}
-		catch (std::exception &)
-		{
-			delete[] screenshot;
-			throw love::Exception("Out of memory.");
-		}
+		[blitencoder copyFromTexture:activeDrawable.texture
+						 sourceSlice:0
+						 sourceLevel:0
+						sourceOrigin:MTLOriginMake(0, 0, 0)
+						  sourceSize:MTLSizeMake(w, h, 0)
+							toBuffer:screenshotbuffer
+				   destinationOffset:0
+			  destinationBytesPerRow:w * 4
+			destinationBytesPerImage:size];
 
-		// TODO
+		submitBlitEncoder();
+	}
 
-		// Replace alpha values with full opacity.
-		for (size_t i = 3; i < size; i += 4)
-			screenshot[i] = 255;
+	for (StreamBuffer *buffer : batchedDrawState.vb)
+		buffer->nextFrame();
+	batchedDrawState.indexBuffer->nextFrame();
+
+	uniformBuffer->nextFrame();
+	uniformBufferData = {};
+	uniformBufferOffset = 0;
+
+	id<MTLCommandBuffer> cmd = getCommandBuffer();
+
+	if (cmd != nil && activeDrawable != nil)
+		[cmd presentDrawable:activeDrawable];
+
+	submitCommandBuffer(SUBMIT_DONE);
+
+	if (!pendingScreenshotCallbacks.empty())
+	{
+		[cmd waitUntilCompleted];
+
+		int w = activeDrawable.texture.width;
+		int h = activeDrawable.texture.height;
+		size_t size = w * h * 4;
 
 		auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
 
@@ -1239,11 +1290,10 @@ void Graphics::present(void *screenshotCallbackData)
 
 			try
 			{
-				img = imagemodule->newImageData(w, h, PIXELFORMAT_RGBA8_UNORM, screenshot);
+				img = imagemodule->newImageData(w, h, PIXELFORMAT_RGBA8_UNORM, screenshotbuffer.contents);
 			}
 			catch (love::Exception &)
 			{
-				delete[] screenshot;
 				info.callback(&info, nullptr, nullptr);
 				for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++)
 				{
@@ -1254,29 +1304,24 @@ void Graphics::present(void *screenshotCallbackData)
 				throw;
 			}
 
+			uint8 *screenshot = (uint8 *) img->getData();
+
+			// Convert from BGRA to RGBA and replace alpha with full opacity.
+			for (size_t i = 0; i < size; i += 4)
+			{
+				uint8 r = screenshot[i + 2];
+				screenshot[i + 2] = screenshot[i + 0];
+				screenshot[i + 0] = r;
+				screenshot[i + 3] = 255;
+			}
+
 			info.callback(&info, img, screenshotCallbackData);
 			img->release();
 		}
 
-		delete[] screenshot;
 		pendingScreenshotCallbacks.clear();
 	}
 
-	for (StreamBuffer *buffer : batchedDrawState.vb)
-		buffer->nextFrame();
-	batchedDrawState.indexBuffer->nextFrame();
-
-	uniformBuffer->nextFrame();
-	uniformBufferData = {};
-	uniformBufferOffset = 0;
-
-	id<MTLCommandBuffer> cmd = getCommandBuffer();
-
-	if (cmd != nil && activeDrawable != nil)
-		[cmd presentDrawable:activeDrawable];
-
-	submitCommandBuffer();
-
 	auto window = Module::getInstance<love::window::Window>(M_WINDOW);
 	if (window != nullptr)
 		window->swapBuffers();
@@ -1288,7 +1333,7 @@ void Graphics::present(void *screenshotCallbackData)
 
 	// Reset the per-frame stat counts.
 	drawCalls = 0;
-	//gl.stats.shaderSwitches = 0;
+	shaderSwitches = 0;
 	renderTargetSwitchCount = 0;
 	drawCallsBatched = 0;
 
@@ -1806,8 +1851,7 @@ void Graphics::initCapabilities()
 
 void Graphics::getAPIStats(int &shaderswitches) const
 {
-	// TODO
-	shaderswitches = 0;
+	shaderswitches = shaderSwitches;
 }
 
 } // metal

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

@@ -737,7 +737,7 @@ void Shader::attach()
 	{
 		Graphics *gfx = Graphics::getInstance();
 		gfx->flushBatchedDraws();
-		gfx->attachShader(this);
+		gfx->setShaderChanged();
 		current = this;
 	}
 }

+ 1 - 1
src/modules/graphics/metal/Texture.mm

@@ -245,7 +245,7 @@ void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, in
 	id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
 
 	gfx->submitBlitEncoder();
-	gfx->submitCommandBuffer();
+	gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
 
 	[cmd waitUntilCompleted];