على جهاز Mac مزوّد بشريحة Apple silicon، ارسم في MTLBuffer مشترك عبر CGContext نقطي، ثم أنشئ نسيج Metal مدعومًا بالمخزن نفسه. تتمثل العقود الأساسية في تطابق بايتات RGBA، ومحاذاة خطوات الصفوف، والاحتفاظ بالتخزين، وترتيب الوصول من CPU وGPU.
ينشئ هذا الدليل مستطيلًا أحمر وبيضاويًا أزرق اصطناعيين، ثم يتحقق من قدرة Metal على قراءة الرسم عبر النسيج. وهو موجّه إلى مطوّر Mac يجهّز رسومات مولّدة لمُصيّر Metal في تطبيق لسطح المكتب. توثّق واجهة برمجة التطبيقات makeTexture(descriptor:offset:bytesPerRow:) من Apple آلية المشاركة.
أي مسار للمشاركة ينبغي أن تختار؟
خصّص التخزين باستخدام Metal أولًا. احصل على عنوانه في CPU باستخدام buffer.contents()، واستخدم هذا العنوان وجهةً لسياق الصور النقطية، وأنشئ النسيج من المخزن. توثّق Apple أن الرسم النقطي يحدّث الذاكرة المقدّمة، وأن النسيج المدعوم بمخزن يشارك التخصيص الخاص به. توفّر واجهات API هذه معًا جسرًا من دون تحميل صورة وسيطة. Graphics Contexts، MTLBuffer.contents()، MTLTexture.
يشترط المثال صراحةً وجود وحدة معالجة رسومات من عائلة Apple. تنص وثائق التخزين الحالية من Apple على أن الأنسجة المشتركة غير متاحة على وحدات معالجة الرسومات غير التابعة لعائلة Apple. اعتبر دعم وحدة معالجة الرسومات شرطًا مسبقًا، ولا تفترض أن كل أجهزة Mac القادرة على تشغيل Metal تدعم استراتيجية التخصيص هذه بعينها. MTLStorageMode.shared.
| متطلبك | النهج المناسب |
|---|---|
| يرسم Core Graphics وتقرأ Metal التخصيص نفسه على Apple silicon | مخزن مشترك مع نسيج مدعوم بمخزن، كما يلي |
| ينبغي أن يستقبل نسيج مخصّص بصورة منفصلة الرسم | حمّل باستخدام replace(region:mipmapLevel:withBytes:bytesPerRow:) إلى نسيج مدعوم يمكن لـCPU الوصول إليه |
| يستفيد الاستخدام المتكرر على GPU من نسيج خاص منفصل | انسخ من مورد مرحلي باستخدام مُرمّز blit |
| لا تستطيع وحدة GPU المحددة إنشاء أنسجة مشتركة | استخدم مسار تحميل أو نسخ مرحلي مدعومًا بدلًا من مثال النسيج المشترك هذا |
تختلف هذه البدائل في الملكية وسلوك النسخ. توثّق Apple أن replace تنفّذ نسخة فورية على CPU، بينما تنقل مُرمّزات blit البيانات بين الموارد، بما فيها التخزين الذي يمكن لـCPU الوصول إليه والتخزين الخاص. يتجنب مسار المشاركة المباشرة نسخة التحميل هذه، لكن Apple تحذّر من أن الأنسجة المدعومة بمخزن قد تحصل على تحسينات أقل للأنسجة. Replacing texture pixels، MTLBlitCommandEncoder، Buffer-backed texture restrictions.
حدّد عقود البكسل والحجم والملكية
استخدم تخطيطًا صريحًا للبايتات: أربعة بايتات لكل بكسل بترتيب العناوين المتزايدة R, G, B, A. في Core Graphics، اجمع premultipliedLast مع byteOrder32Big؛ وفي Metal، استخدم rgba8Unorm. يجعل إعداد ترتيب البايتات تفسير الذاكرة صريحًا، بصرف النظر عن تمثيل عدد صحيح أصلي. توثّق Apple موضع ألفا، وعناصر التحكم في ترتيب البايتات، ومكوّنات Metal الأربعة المطبّعة لـRGBA كلًا على حدة. CGImageAlphaInfo.premultipliedLast، CGBitmapInfo، MTLPixelFormat.rgba8Unorm.
تعني ألفا المضروبة مسبقًا أن قنوات اللون المخزّنة تتضمن ألفا بالفعل. على سبيل التوضيح، تُخزّن قيمة الأحمر الكامل نصف الشفافية فوق تخزين شفاف تقريبًا على النحو 128, 0, 0, 128، مع مراعاة التقريب الكمي. يجب أن يحافظ التظليل اللاحق على هذا التفسير؛ فضرب RGB المأخوذ بالعينة في ألفا مرة أخرى سيجعله أغمق. يستخدم المثال أشكالًا معتمة لتبسيط فحص البايتات. Bitmap Images and Image Masks.
يختار المثال CGColorSpace.linearSRGB، الذي تعرّفه Apple بأنه قياس لوني sRGB مع دالة نقل خطية. يحدّد هذا تفسير ألوان الرسم صراحةً؛ فمشاركة الذاكرة بحد ذاتها لا تُجري أي تحويل لمساحة الألوان. حافظ على اتساق هذا التفسير عند دمج النسيج في مُصيّر. CGColorSpace.linearSRGB.
احسب خطوة الصف قبل التخصيص
طول الصف المرئي هو width × 4، لكن قد تكون خطوة التخزين أكبر. استعلم عن minimumLinearTextureAlignment(for:) للجهاز والتنسيق المحددين، ثم قرّب طول الصف المرئي إلى أعلى ليكون من مضاعفات تلك المحاذاة. أعطِ Core Graphics وMetal القيمة الناتجة نفسها لـbytesPerRow. minimumLinearTextureAlignment(for:).
عند افتراض محاذاة توضيحية صريحة للجهاز مقدارها 256 بايت، تحتاج صورة بحجم 257 × 128 إلى 1,028 بايت مرئيًا لكل صف، وخطوة محاذاة مقدارها 1,280 بايت، وإجمالي 163,840 بايت مخصّص. إن 252 بايت الإضافية لكل صف هي حشو وليست بكسلات إضافية. يستعلم البرنامج عن المحاذاة الفعلية بدلًا من افتراض هذه القيمة التوضيحية.
خصّص ما لا يقل عن bytesPerRow × height بايت. بالنسبة إلى بكسل عند عمود الذاكرة x وصفّ y، يكون إزاحته بالبايت y × bytesPerRow + x × 4. عند تكييف المثال ذي الأبعاد الثابتة للعمل بأبعاد متغيرة، ارفض الأحجام غير الموجبة وافحص الضرب والتقريب بحثًا عن تجاوز صحيح عددي قبل التخصيص. توثّق Core Graphics متطلب التخزين الكامل، أي حاصل ضرب خطوة الصف في الارتفاع. Creating a Bitmap Graphics Context.
أبقِ التخصيص حيًا طوال مدة استخدامه
في هذا التصميم، تخصص Metal الذاكرة. يستعير سياق الصور النقطية عنوانها؛ لذا أبقِ المخزن حيًا طوال مدة السياق. وبالنسبة إلى عمل GPU غير المتزامن، احتفظ أيضًا بالموارد حتى اكتمال ذلك العمل. لا تحرر buffer.contents() بنفسك ولا تستبدله بمؤشر مأخوذ من وصول مؤقت إلى مصفوفة Swift.
ينهي البرنامج سياق الرسم ويحرره قبل إرسال عمل GPU، ثم يُبقي المخازن والنسيج متاحة حتى اكتمال العمل. يتوافق هذا مع نموذج ذاكرة الصور النقطية المقدّمة من المستدعي ومتطلبات Metal الموثّقة لترتيب عمل CPU وGPU. Graphics Contexts، Synchronizing CPU and GPU work.
أنشئ مثال رسم اصطناعيًا كاملًا
أنشئ مشروع سطر أوامر لـmacOS، أو احفظ البرنامج الكامل التالي باسم main.swift. استخدم حزمة تطوير برامج macOS حديثة من Xcode: يستخدم الكود مُهيّئ CGContext الحالي الذي وسيط bitmapInfo فيه من النوع CGBitmapInfo. قد تعرض حزم SDK الأقدم المُهيّئ القديم الذي يتوقع bitmapInfo.rawValue بدلًا منه. CGContext bitmap initializer.
يجمع الرسم بين استدعاءات Core Graphics المباشرة وبيضاوي من AppKit. يغلّف NSGraphicsContext(cgContext:flipped:) السياق الموجود، لذا يرسم كلاهما في الوجهة نفسها. يؤدي حفظ حالة الرسومات واستعادتها في AppKit إلى استعادة السياق الحالي السابق بعد الرسم. NSGraphicsContext initializer، saveGraphicsState()، restoreGraphicsState().
import AppKit
import CoreGraphics
import Metal
// Fixed, small dimensions keep all size arithmetic bounded.
let width = 257
let height = 128
let format: MTLPixelFormat = .rgba8Unorm
guard let device = MTLCreateSystemDefaultDevice(),
device.supportsFamily(.apple1) else {
fatalError("This example requires an Apple-family GPU.")
}
let alignment = device.minimumLinearTextureAlignment(for: format)
guard alignment > 0 else { fatalError("Invalid alignment.") }
let rowBytes = ((width * 4 + alignment - 1) / alignment) * alignment
let byteCount = rowBytes * height
guard let buffer = device.makeBuffer(
length: byteCount, options: .storageModeShared
) else { fatalError("Drawing buffer allocation failed.") }
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: format, width: width, height: height, mipmapped: false
)
descriptor.storageMode = .shared
descriptor.usage = .shaderRead
guard let texture = buffer.makeTexture(
descriptor: descriptor, offset: 0, bytesPerRow: rowBytes
) else { fatalError("Buffer-backed texture creation failed.") }
precondition(texture.buffer === buffer)
// This scope finishes CPU drawing before GPU submission.
do {
guard let space = CGColorSpace(name: CGColorSpace.linearSRGB) else {
fatalError("Color space creation failed.")
}
let info = CGBitmapInfo(
rawValue: CGImageAlphaInfo.premultipliedLast.rawValue
).union(.byteOrder32Big)
guard let cg = CGContext(
data: buffer.contents(), width: width, height: height,
bitsPerComponent: 8, bytesPerRow: rowBytes,
space: space, bitmapInfo: info
) else { fatalError("Bitmap context creation failed.") }
cg.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
cg.fill(CGRect(x: 0, y: 0,
width: CGFloat(width), height: CGFloat(height)))
NSGraphicsContext.saveGraphicsState()
defer { NSGraphicsContext.restoreGraphicsState() }
NSGraphicsContext.current = NSGraphicsContext(
cgContext: cg, flipped: false
)
NSColor.blue.setFill()
NSBezierPath(ovalIn: NSRect(x: 32, y: 24, width: 48, height: 48)).fill()
}
// Diagnostic copy: prove the GPU can read through the texture.
guard let readback = device.makeBuffer(
length: byteCount, options: .storageModeShared
), let queue = device.makeCommandQueue(),
let command = queue.makeCommandBuffer(),
let blit = command.makeBlitCommandEncoder() else {
fatalError("Readback setup failed.")
}
blit.copy(
from: texture, sourceSlice: 0, sourceLevel: 0,
sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
sourceSize: MTLSize(width: width, height: height, depth: 1),
to: readback, destinationOffset: 0,
destinationBytesPerRow: rowBytes, destinationBytesPerImage: 0
)
blit.endEncoding()
command.commit()
command.waitUntilCompleted()
guard command.status == .completed else {
fatalError("GPU copy failed: \(String(describing: command.error))")
}
let pixel = readback.contents().assumingMemoryBound(to: UInt8.self)
precondition(pixel[0] == 255 && pixel[1] == 0 &&
pixel[2] == 0 && pixel[3] == 255)
withExtendedLifetime(buffer) {
print("Shared RGBA texture verified; row stride: \(rowBytes) bytes")
}
ترجمه وشغّله على جهاز Mac المستهدف:
xcrun swiftc main.swift -o SharedRGBA -framework AppKit -framework CoreGraphics -framework Metal
./SharedRGBA
تتحقق خاصية buffer في النسيج من مورده الداعم. ثم ينسخ التشخيص بكسلات النسيج إلى مخزن مشترك منفصل ويفحص زاوية حمراء لم تُمس بعد اكتمال GPU. هذه القراءة الراجعة نسخة اختبار مقصودة، وهي منفصلة عن تخصيص الرسم المشترك. توثّق Apple عملية النسخ من نسيج إلى مخزن وتحدد destinationBytesPerImage: 0 لنسخة ثنائية الأبعاد. MTLTexture.buffer، Texture-to-buffer copy.
تم التحقق من حسابات الخطوة باستخدام مدخلات عيّنة حتمية. جرت مراجعة برنامج Swift بمقارنته بواجهات API التي جرى استرجاعها، لكن تعذّر تجميعه أو تشغيله في بيئة البحث، إذ توفر Linux من دون Swift أو أطر Apple.
نسّق بين الرسم على CPU واستخدام GPU
لا يلغي التخزين المشترك الحاجة إلى جدولة الوصول. أنهِ الرسم على CPU قبل اعتماد الأوامر التي تقرأ النسيج. وبعد الإرسال، اترك ذلك التخصيص دون تغيير حتى تنتهي GPU من استخدامه. تشترط Apple صراحةً هذا التنسيق للموارد المشتركة. MTLStorageMode.shared.
في المثال الذي يعمل مرة واحدة، يجعل waitUntilCompleted() تسليم العمل سهل التتبع: فهو يحجب التنفيذ حتى ينتهي مخزن الأوامر ومعالجات اكتماله. في مُصيّر تفاعلي، تجنب وضع انتظار حاظر كهذا في كل إطار. بدلًا من ذلك، احتفظ بأزواج متعددة من المخازن والأنسجة، وأعِد كل زوج إلى مجموعة المتاح فقط بعد إشعار اكتمال GPU. تعرض Apple نمط تجميع الموارد هذا في نموذج المزامنة الخاص بها. waitUntilCompleted()، Synchronizing CPU and GPU work.
إذا جعلت GPU لاحقًا تكتب عبر اسم مستعار وتقرأ عبر اسم آخر، فأعد مراجعة المزامنة. تشير Apple إلى أن بيانات النسيج المدعوم بمخزن قد تكون متماسكة بين مراحل التصيير من دون أن تعكس تغييرات الأسماء المستعارة فورًا داخل مرحلة واحدة. يتجنب المثال هذا التعقيد بإكمال الرسم على CPU قبل عملية القراءة الوحيدة على GPU. Buffer-backed texture coherence.
شخّص مشكلات التخطيط قبل تغيير المُصيّر
استخدم التسلسل التالي لعزل العقد الذي أخفق. تستند هذه الفحوص إلى قواعد تخطيط البكسل والمورد الداعم والخطوة والمزامنة الموثّقة أعلاه.
- فشل إنشاء النسيج: تأكد من أن GPU تدعم الأنسجة المشتركة، وأن كلا الموردين يستخدمان
.shared، وأن الإزاحة والخطوة تستوفيان المحاذاة التي جرى الاستعلام عنها. أبقِ النسيج ثنائي الأبعاد، بعينة واحدة، ومستوى mip واحد، وعنصر مصفوفة واحد. - ظهور الأحمر باللون الأزرق: افحص البايتات فرادى. يتطلب المثال ترتيب بايتات RGBA، و
premultipliedLast، وbyteOrder32Big، و.rgba8Unormمعًا. - ظهور الصفوف مائلة: قارن خطوتَي السياق والنسيج. يجب أن تستخدما كلتاهما
rowBytes، بما في ذلك الحشو. - ظهور الصورة مقلوبة: افحص تحويل الرسم وربط إحداثيات النسيج في المُصيّر. يستخدم المثال سياق رسم غير مقلوب. يصف وسيط
flippedفي AppKit حالة القلب الأولية؛ ولا يعيد ترتيب الصفوف المخزّنة. - ظهور التحديثات بصورة غير متسقة: تحقق مما إذا كان الرسم على CPU يتداخل مع وصول GPU إلى التخصيص نفسه. استخدم تتبع الاكتمال قبل إعادة استخدامه.
تأتي قيود الإنشاء من وثائق الأنسجة الخطية لدى Apple، ويأتي التمييز بين الحالتين المقلوبة وغير المقلوبة من مُهيّئ AppKit. اجعل معالجة الاتجاه صريحة عند إضافة تظليل العرض الخاص بك. Linear-texture requirements، AppKit flipped-state parameter.
بمجرد أن يتوافق المخزن الداعم وتخطيط البايتات والخطوة المحشوة وجدول الوصول، يصبح الرسم الاصطناعي متاحًا لـMetal عبر النسيج المشترك. حافظ على هذه العقود عند تغيير حجم الصورة أو إضافة عمليات رسم أخرى؛ واختر نسخة مرحلية عندما تتطلب وحدة GPU المستهدفة أو متطلبات النسيج اللاحقة تخزينًا منفصلًا.
المصادر
MTLTexture — Apple
MTLStorageMode.shared — Apple
contents() — Apple
Graphics Contexts — Apple
Bitmap Images and Image Masks — Apple
CGBitmapInfo — Apple
MTLPixelFormat.rgba8Unorm — Apple
linearSRGB — Apple
init(data:width:height:bitsPerComponent:bytesPerRow:space:bitmapInfo:) — Apple
init(cgContext:flipped:) — Apple
saveGraphicsState() — Apple
restoreGraphicsState() — Apple
buffer — Apple
Synchronizing CPU and GPU work — Apple
waitUntilCompleted() — Apple
MTLBlitCommandEncoder — Apple