Tổng quan về Metal trong iOS
Metal là framework đồ họa và tính toán hiệu năng cao của Apple, được thiết kế để tận dụng tối đa phần cứng GPU. Nó hỗ trợ cả rendering (vẽ đồ họa) và compute (tính toán song song), giúp tăng tốc xử lý đồ họa và các tác vụ tính toán nặng như xử lý ảnh, machine learning, v.v.
Lợi ích khi sử dụng Metal
- Hiệu suất cao: Tận dụng tối đa GPU, giảm tải cho CPU.
- Tính toán song song: Dễ dàng thực hiện các phép toán trên ma trận, hình ảnh.
- Truy cập API cấp thấp: Linh hoạt hơn so với OpenGL ES.
- Tương thích đa nền tảng của Apple: Chạy trên iOS, macOS và tvOS.
Ứng dụng Metal trong xử lý ảnh
Metal có thể áp dụng để xử lý ảnh bằng cách sử dụng Compute Shader. Đây là một loại shader dùng để xử lý dữ liệu trên GPU mà không cần liên quan đến rendering.
Các bước cơ bản để áp dụng bộ lọc ảnh với Metal
-
Tạo một Metal Compute Shader (Kernel Shader)
- Đây là đoạn code chạy trên GPU để xử lý từng pixel của ảnh.
-
Tải ảnh vào một Metal Texture
- Chuyển ảnh từ CPU vào GPU để xử lý.
-
Thiết lập Pipeline & Command Buffer
- Tạo pipeline để gửi công việc đến GPU.
-
Thực thi Kernel Shader trên GPU
- Chạy bộ lọc ảnh trên GPU.
-
Nhận kết quả và hiển thị ảnh đã xử lý
- Chuyển ảnh đã xử lý về CPU hoặc hiển thị lên màn hình.
Ví dụ: Áp dụng bộ lọc GrayScale với Metal
1. Viết Compute Shader (GrayScaleKernel.metal)
#include <metal_stdlib>
using namespace metal;
kernel void grayscaleKernel(texture2d<float, access::read> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
uint2 gid [[thread_position_in_grid]]) {
if (gid.x >= outputTexture.get_width() || gid.y >= outputTexture.get_height()) return;
float4 color = inputTexture.read(gid);
float gray = dot(color.rgb, float3(0.299, 0.587, 0.114));
outputTexture.write(float4(gray, gray, gray, color.a), gid);
}
2. Xử lý ảnh trong Swift
import Metal
import MetalKit
class MetalImageProcessor {
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
var pipelineState: MTLComputePipelineState!
init() {
device = MTLCreateSystemDefaultDevice()
commandQueue = device.makeCommandQueue()
let library = device.makeDefaultLibrary()
let function = library?.makeFunction(name: "grayscaleKernel")
pipelineState = try! device.makeComputePipelineState(function: function!)
}
func applyGrayScale(to inputTexture: MTLTexture) -> MTLTexture {
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: .rgba8Unorm, width: inputTexture.width, height: inputTexture.height, mipmapped: false
)
descriptor.usage = [.shaderRead, .shaderWrite]
let outputTexture = device.makeTexture(descriptor: descriptor)!
let commandBuffer = commandQueue.makeCommandBuffer()!
let commandEncoder = commandBuffer.makeComputeCommandEncoder()!
commandEncoder.setComputePipelineState(pipelineState)
commandEncoder.setTexture(inputTexture, index: 0)
commandEncoder.setTexture(outputTexture, index: 1)
let threadGroupSize = MTLSize(width: 16, height: 16, depth: 1)
let threadGroups = MTLSize(
width: (inputTexture.width + 15) / 16,
height: (inputTexture.height + 15) / 16,
depth: 1
)
commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)
commandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
return outputTexture
}
}
Mở rộng: Một số bộ lọc ảnh thông dụng khác
-
Làm mờ ảnh (Gaussian Blur)
- Áp dụng convolution kernel để làm mờ ảnh.
-
Tăng độ nét (Sharpening)
- Dùng kernel lọc high-pass để tăng chi tiết.
-
Bộ lọc Sepia
- Chuyển ảnh sang tông màu nâu cổ điển.
-
Edge Detection (Phát hiện cạnh)
- Áp dụng bộ lọc Sobel hoặc Laplacian.
Chi tiết ví dụ về bộ lọc Sharpening với Metal trên iOS
Bộ lọc Sharpening giúp làm tăng độ nét của hình ảnh bằng cách sử dụng kernel convolution. Chúng ta sẽ sử dụng một Compute Shader để áp dụng bộ lọc này.
1. Viết Compute Shader (SharpenKernel.metal)
Bộ lọc sharpening sử dụng kernel 3x3 để tăng cường độ tương phản của các cạnh.
#include <metal_stdlib>
using namespace metal;
// Kernel sharpening filter
constant float3x3 sharpenKernel = float3x3(
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
);
kernel void sharpenKernel(texture2d<float, access::read> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
uint2 gid [[thread_position_in_grid]]) {
if (gid.x >= outputTexture.get_width() || gid.y >= outputTexture.get_height()) return;
int2 offsets[9] = {
int2(-1, -1), int2(0, -1), int2(1, -1),
int2(-1, 0), int2(0, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1)
};
float3 resultColor = float3(0.0);
for (int i = 0; i < 9; i++) {
int2 coord = int2(gid) + offsets[i];
if (coord.x >= 0 && coord.x < inputTexture.get_width() &&
coord.y >= 0 && coord.y < inputTexture.get_height()) {
resultColor += inputTexture.read(uint2(coord)).rgb * sharpenKernel[i / 3][i % 3];
}
}
outputTexture.write(float4(clamp(resultColor, 0.0, 1.0), 1.0), gid);
}
2. Xử lý ảnh bằng Swift
Dưới đây là code để tải ảnh, áp dụng bộ lọc Sharpening, và hiển thị ảnh đã xử lý.
import Metal
import MetalKit
class MetalImageProcessor {
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
var pipelineState: MTLComputePipelineState!
init() {
device = MTLCreateSystemDefaultDevice()
commandQueue = device.makeCommandQueue()
let library = device.makeDefaultLibrary()
let function = library?.makeFunction(name: "sharpenKernel")
pipelineState = try! device.makeComputePipelineState(function: function!)
}
func applySharpening(to inputTexture: MTLTexture) -> MTLTexture {
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: .rgba8Unorm, width: inputTexture.width, height: inputTexture.height, mipmapped: false
)
descriptor.usage = [.shaderRead, .shaderWrite]
let outputTexture = device.makeTexture(descriptor: descriptor)!
let commandBuffer = commandQueue.makeCommandBuffer()!
let commandEncoder = commandBuffer.makeComputeCommandEncoder()!
commandEncoder.setComputePipelineState(pipelineState)
commandEncoder.setTexture(inputTexture, index: 0)
commandEncoder.setTexture(outputTexture, index: 1)
let threadGroupSize = MTLSize(width: 16, height: 16, depth: 1)
let threadGroups = MTLSize(
width: (inputTexture.width + 15) / 16,
height: (inputTexture.height + 15) / 16,
depth: 1
)
commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)
commandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
return outputTexture
}
}
3. Cách sử dụng trong ViewController
Bạn có thể tải ảnh và áp dụng bộ lọc sharpening như sau:
import UIKit
import MetalKit
class ViewController: UIViewController {
var metalProcessor: MetalImageProcessor!
var mtkView: MTKView!
override func viewDidLoad() {
super.viewDidLoad()
metalProcessor = MetalImageProcessor()
// Tạo MTKView để hiển thị ảnh
mtkView = MTKView(frame: self.view.bounds, device: metalProcessor.device)
self.view.addSubview(mtkView)
// Tải ảnh
guard let textureLoader = try? MTKTextureLoader(device: metalProcessor.device),
let url = Bundle.main.url(forResource: "sample", withExtension: "jpg"),
let inputTexture = try? textureLoader.newTexture(URL: url, options: nil) else {
return
}
// Áp dụng bộ lọc sharpening
let outputTexture = metalProcessor.applySharpening(to: inputTexture)
// Gán ảnh đã xử lý vào MTKView
mtkView.framebufferOnly = false
mtkView.drawableSize = CGSize(width: outputTexture.width, height: outputTexture.height)
let commandBuffer = metalProcessor.commandQueue.makeCommandBuffer()!
let drawable = mtkView.currentDrawable!
let blitEncoder = commandBuffer.makeBlitCommandEncoder()!
blitEncoder.copy(from: outputTexture, to: drawable.texture)
blitEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
Giải thích code
1. Compute Shader (sharpenKernel.metal)
• Sử dụng ma trận sharpening 3x3 để tăng độ nét.
• Duyệt qua từng pixel và nhân với giá trị kernel tương ứng.
• Dùng clamp() để tránh giá trị pixel bị vượt quá [0,1].
2. Lớp MetalImageProcessor (Swift)
• Khởi tạo Metal và pipeline xử lý ảnh.
• Tạo texture đầu vào và đầu ra.
• Thực thi Compute Shader trên GPU.
3. ViewController
• Tải ảnh vào MTKView.
• Áp dụng bộ lọc sharpenKernel.
• Hiển thị ảnh đã xử lý trên màn hình.
Kết quả
• Ảnh sau khi áp dụng bộ lọc sẽ có độ nét cao hơn, giúp làm rõ các chi tiết và cạnh trong ảnh.
• Thời gian xử lý nhanh hơn so với CPU nhờ GPU xử lý song song.
Kết luận
Metal giúp xử lý ảnh nhanh chóng bằng cách tận dụng GPU. Khi làm việc với Metal, bạn có thể tạo các bộ lọc ảnh tùy chỉnh với hiệu suất cao hơn so với Core Image hoặc CPU thông thường. Nếu bạn đang làm việc với ứng dụng xử lý ảnh trên iOS, Metal là một lựa chọn mạnh mẽ để tối ưu hiệu suất. 🚀

