{"id":481751,"date":"2026-05-31T09:26:21","date_gmt":"2026-05-31T09:26:21","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=481751"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=481751","title":{"rendered":"\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 \u043d\u0430 Mac \u0441\u0432\u043e\u0438\u043c\u0438 \u0440\u0443\u043a\u0430\u043c\u0438: Metal, \u043e\u043a\u043d\u0430 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430 \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0438"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u042f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 NeonDrift \u2014 \u0436\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 \u0434\u043b\u044f macOS \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 Metal-\u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432. \u0414\u043b\u044f \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, Screen Recording \u0438\u043b\u0438 Accessibility-\u0434\u043e\u0441\u0442\u0443\u043f. \u0422\u043e\u043b\u044c\u043a\u043e AppKit, MetalKit \u0438 SwiftUI.<\/p>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0443 \u043a\u0430\u043a \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u043e\u0442 \u0442\u0440\u044e\u043a\u0430 \u0441 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 \u043e\u043a\u043e\u043d \u0434\u043e \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 \u0438 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0432 <code>.app<\/code>. \u041f\u043e\u043f\u0443\u0442\u043d\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0431\u0430\u0433\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043f\u043e\u0439\u043c\u0430\u043b \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u2014 \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u0443\u044e \u043f\u043b\u0430\u0437\u043c\u0443 \u043d\u0430 Retina, \u043a\u0440\u044d\u0448 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Space, \u0438 \u0444\u0440\u0438\u0437\u044b \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0435 \u043f\u0440\u0438 \u0441\u043c\u0435\u043d\u0435 Space \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c.<\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0435 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d wallpaper app, \u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430 macOS \u043c\u043e\u0436\u043d\u043e \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u0442\u044c AppKit window management, Metal render loop \u0438 SwiftUI-\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 API \u2014 \u0438 \u0433\u0434\u0435 \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0442\u0440\u0435\u0449\u0430\u0442\u044c \u043f\u043e \u0448\u0432\u0430\u043c.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/022\/9dd\/fb1\/0229ddfb1d37e5b5f546873484c57602.png\" alt=\"\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 NeonDrift \u043d\u0430 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u0442\u043e\u043b\u0435\" title=\"\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 NeonDrift \u043d\u0430 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u0442\u043e\u043b\u0435\" width=\"1560\" height=\"1013\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/022\/9dd\/fb1\/0229ddfb1d37e5b5f546873484c57602.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/022\/9dd\/fb1\/0229ddfb1d37e5b5f546873484c57602.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 NeonDrift \u043d\u0430 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u0442\u043e\u043b\u0435<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u0418\u0434\u0435\u044f: \u043d\u0435 \u043c\u0435\u043d\u044f\u0442\u044c \u043e\u0431\u043e\u0438, \u0430 \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0435\u0440\u0445 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430<\/h3>\n<p>macOS \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e API \u0434\u043b\u044f \u0436\u0438\u0432\u044b\u0445 \u043e\u0431\u043e\u0435\u0432. \u041d\u043e \u0435\u0441\u0442\u044c \u043e\u0431\u0445\u043e\u0434\u043d\u043e\u0439 \u043f\u0443\u0442\u044c: \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u043e\u0435 <code>NSWindow<\/code> \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0440\u044f\u0434\u043e\u043c \u0441 desktop layer \u2014 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e \u043a\u0430\u043a \u0444\u043e\u043d: \u043d\u0435 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u043b\u043e \u043a\u043b\u0438\u043a\u0438, \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u043b\u043e\u0441\u044c \u0432 Mission Control \u0438 \u043d\u0435 \u043a\u043e\u043d\u043a\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u043e \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c\u0438 \u043e\u043a\u043d\u0430\u043c\u0438.<\/p>\n<p>\u042d\u0442\u043e \u043d\u0435 exploit: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API \u0443\u0440\u043e\u0432\u043d\u0435\u0439 \u043e\u043a\u043e\u043d \u2014 <code>CGWindowLevelForKey(.desktopWindow)<\/code>. \u041d\u043e \u044d\u0442\u043e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e window-level hack, \u0430 \u043d\u0435 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 wallpaper API. \u0415\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 macOS \u0438 \u0440\u0435\u0436\u0438\u043c\u044b \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430: Stage Manager, Spaces, Full Screen \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u044f \u0438\u043d\u0430\u0447\u0435.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 1: \u043e\u043a\u043d\u043e \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430<\/h3>\n<p>\u0412\u043e\u0442 \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u201c\u043e\u0431\u043e\u0439\u043d\u043e\u0433\u043e\u201d \u043e\u043a\u043d\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430:<\/p>\n<pre><code class=\"swift\">let window = NSWindow(    contentRect: screen.frame,    styleMask: [.borderless],    backing: .buffered,    defer: false,    screen: screen)window.backgroundColor = .blackwindow.isOpaque = truewindow.hasShadow = falsewindow.animationBehavior = .nonewindow.isReleasedWhenClosed = false\/\/ \u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0438\u0442 \u0432\u0441\u0435 \u043a\u043b\u0438\u043a\u0438 \u043f\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u043b\u0443window.ignoresMouseEvents = true\/\/ \u041f\u0440\u0438\u043b\u0438\u043f\u0430\u0435\u0442 \u043a\u043e \u0432\u0441\u0435\u043c Space, \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Mission Control\/Expos\u00e9window.collectionBehavior = [    .canJoinAllSpaces,    .stationary,    .ignoresCycle,    .fullScreenAuxiliary  \/\/ \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u0432\/\u0438\u0437 Full Screen]\/\/ \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u043a\u043d\u043e \u043d\u0430\u0434 desktop layer, \u043d\u043e \u043d\u0438\u0436\u0435 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043e\u043a\u043e\u043dwindow.level = NSWindow.Level(    rawValue: Int(CGWindowLevelForKey(.desktopWindow)) + 1)window.setFrame(screen.frame, display: true)\/\/ \u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c \u043e\u043a\u043d\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e level \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043e\u043a\u043d\u0443.window.order(.above, relativeTo: 0)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u0432\u0430 \u043c\u043e\u043c\u0435\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u0430\u0436\u0443\u0442\u0441\u044f \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u043c\u0438, \u043d\u043e \u0431\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442:<\/p>\n<p><code>ignoresMouseEvents = true<\/code> \u2014 \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043a\u043b\u0438\u043a\u0438 \u043f\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u043b\u0443. \u042f \u0437\u0430\u0431\u044b\u043b \u044d\u0442\u043e \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043f\u0440\u043e\u0432\u0451\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442 \u0432 \u043d\u0435\u0434\u043e\u0443\u043c\u0435\u043d\u0438\u0438, \u043f\u043e\u0447\u0435\u043c\u0443 \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0430\u043f\u043a\u0438.<\/p>\n<p><code>.fullScreenAuxiliary<\/code> \u0432 <code>collectionBehavior<\/code> \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u0438\u043b\u0438 \u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u044f \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u0432\/\u0438\u0437 Full Screen spaces. \u041e\u043d\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442, \u043d\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0435\u0439: \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0435 \u0438\u0437 full screen \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u0435\u0440\u0441\u0438\u0438 macOS.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 2: Metal pipeline \u0438 render loop<\/h3>\n<p>\u0414\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u0435\u043d Metal. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, command queue \u0438 pipeline:<\/p>\n<pre><code class=\"swift\">guard let device = MTLCreateSystemDefaultDevice() else {    throw RuntimeError(\"Metal \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 \u044d\u0442\u043e\u043c Mac.\")}guard let commandQueue = device.makeCommandQueue() else {    throw RuntimeError(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c command queue.\")}\/\/ \u0428\u0435\u0439\u0434\u0435\u0440\u044b \u0433\u0440\u0443\u0437\u044f\u0442\u0441\u044f \u0438\u0437 .metal \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0431\u0430\u043d\u0434\u043b\u0435 \u043a\u0430\u043a \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430,\/\/ \u0430 \u043d\u0435 \u0438\u0437 default library \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e default library \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f\/\/ \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u0431\u043e\u0440\u043a\u0438, \u0430 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0448\u0435\u0439\u0434\u0435\u0440\u044b \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432let source = try loadShaderSource()let library = try device.makeLibrary(source: source, options: nil)let descriptor = MTLRenderPipelineDescriptor()descriptor.vertexFunction   = library.makeFunction(name: \"vs_main\")descriptor.fragmentFunction = library.makeFunction(name: \"fs_main\")descriptor.colorAttachments[0].pixelFormat = .bgra8Unormlet pipelineState = try device.makeRenderPipelineState(descriptor: descriptor)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>MTKView<\/code> \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 device, \u043a\u043b\u0430\u0434\u0451\u0442\u0441\u044f \u0432 window \u043a\u0430\u043a contentView, \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0443 \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0443:<\/p>\n<pre><code class=\"swift\">let view = MTKView(frame: NSRect(origin: .zero, size: screen.frame.size))view.device = deviceview.colorPixelFormat = .bgra8Unormview.framebufferOnly = trueview.isPaused = falseview.enableSetNeedsDisplay = falseview.preferredFramesPerSecond = 60window.contentView = view<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0420\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 <code>MTKViewDelegate<\/code>. \u041f\u043e\u043c\u0438\u043c\u043e <code>draw(in:)<\/code> \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c <code>mtkView(_:drawableSizeWillChange:)<\/code> \u2014 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f lifecycle-\u0442\u043e\u0447\u043a\u0430 \u0434\u043b\u044f resize, Retina \u0438 hotplug:<\/p>\n<pre><code class=\"swift\">func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {    \/\/ \u0422\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0441\u0447\u0451\u0442\u0430 size-dependent \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432.    \/\/ \u0423 \u043d\u0430\u0441 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u2014 resolution \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f    \/\/ \u0447\u0435\u0440\u0435\u0437 uniforms \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0430\u0434\u0440. \u041d\u043e \u043c\u0435\u0442\u043e\u0434 \u043d\u0443\u0436\u0435\u043d \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e lifecycle.}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0435\u0441\u044c \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 <code>draw(in:)<\/code>:<\/p>\n<pre><code class=\"swift\">func draw(in view: MTKView) {    guard        let descriptor = view.currentRenderPassDescriptor,        let drawable   = view.currentDrawable,        let buffer     = commandQueue.makeCommandBuffer(),        let encoder    = buffer.makeRenderCommandEncoder(descriptor: descriptor)    else { return }    var uniforms = Uniforms(        time:       Float(CACurrentMediaTime() - startTime) * animationSpeed,        resolution: SIMD2(Float(view.drawableSize.width),                          Float(view.drawableSize.height)),        \/\/ ... \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0442\u0435\u043c\u044b    )    encoder.setRenderPipelineState(pipelineState)    encoder.setFragmentBytes(&amp;uniforms, length: MemoryLayout&lt;Uniforms&gt;.stride, index: 0)    encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)    encoder.endEncoding()    buffer.present(drawable)    buffer.commit()}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041f\u0440\u043e <\/strong><code><strong>drawableSize<\/strong><\/code><strong> vs <\/strong><code><strong>bounds<\/strong><\/code><strong>:<\/strong> \u043f\u0435\u0440\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0448\u0435\u0439\u0434\u0435\u0440\u0430 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0441 <code>view.bounds.size<\/code> \u2014 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u0443\u044e \u043f\u043b\u0430\u0437\u043c\u0443 \u043d\u0430 Retina. <code>bounds<\/code> \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0432 points, \u0430 <code>in.position.xy<\/code> \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u043c \u0448\u0435\u0439\u0434\u0435\u0440\u0435 \u2014 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438. \u041d\u0430 Retina-\u0434\u0438\u0441\u043f\u043b\u0435\u0435 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 2\u00d7, \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0441\u0436\u0438\u043c\u0430\u043b\u0430\u0441\u044c \u0432 \u043b\u0435\u0432\u044b\u0439 \u043d\u0438\u0436\u043d\u0438\u0439 \u0443\u0433\u043e\u043b \u0438 \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u043b\u0430\u0441\u044c \u043f\u043e viewport. <code>view.drawableSize<\/code> \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438 \u2014 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043c\u0435\u043d\u044b \u0432\u0441\u0451 \u0432\u0441\u0442\u0430\u043b\u043e \u043d\u0430 \u043c\u0435\u0441\u0442\u043e.<\/p>\n<p><strong>\u041f\u0440\u043e <\/strong><code><strong>setFragmentBytes<\/strong><\/code><strong>:<\/strong> \u0443\u0434\u043e\u0431\u0435\u043d \u0434\u043b\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e uniforms-\u0431\u043b\u043e\u043a\u0430 (\u0443 \u043d\u0430\u0441 ~120 \u0431\u0430\u0439\u0442). \u0415\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0430\u0441\u0441\u0438\u0432\u044b \u0438\u043b\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u2014 \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 <code>MTLBuffer<\/code>.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 3: \u0448\u0435\u0439\u0434\u0435\u0440 \u2014 \u0432\u0441\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>\u0412\u0435\u0440\u0442\u0435\u043a\u0441\u043d\u044b\u0439 \u0448\u0435\u0439\u0434\u0435\u0440 \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u0435\u043d \u2014 \u043e\u0434\u0438\u043d \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a \u043d\u0430 \u0432\u0435\u0441\u044c \u044d\u043a\u0440\u0430\u043d:<\/p>\n<pre><code>vertex VertexOut vs_main(uint vertexID [[vertex_id]]) {    float2 positions[3] = {        float2(-1.0, -1.0),        float2( 3.0, -1.0),        float2(-1.0,  3.0),    };    VertexOut out;    out.position = float4(positions[vertexID], 0.0, 1.0);    return out;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0441\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u2014 \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u043c \u0448\u0435\u0439\u0434\u0435\u0440\u0435. \u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u043b\u0430\u0437\u043c\u044b:<\/p>\n<pre><code>fragment float4 fs_main(VertexOut in [[stage_in]],                         constant Uniforms &amp;u [[buffer(0)]]) {    \/\/ in.position.xy \u2014 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438, u.resolution \u2014 \u0442\u043e\u0436\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438    float2 uv = in.position.xy \/ u.resolution;    float2 p  = uv * 2.0 - 1.0;    p.x *= u.resolution.x \/ u.resolution.y;    float t = u.time * 0.4;    float v = sin(p.x * 3.0 + t)            + sin(p.y * 2.5 - t * 0.7)            + sin((p.x + p.y) * 2.0 + t * 1.3)            + sin(length(p) * 4.0 - t * 2.0);    v = v * 0.25 + 0.5;    return float4(palette(v, u.palettePreset), 1.0);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0434\u043b\u044f \u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u043d\u043e\u0439 \u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u2014 \u0442\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d Shadertoy. \u0412\u043c\u0435\u0441\u0442\u043e \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0438 \u0440\u0438\u0441\u0443\u0435\u043c \u043e\u0434\u0438\u043d \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0448\u0435\u0439\u0434\u0435\u0440 \u0441\u0430\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0446\u0432\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e4c\/aeb\/985\/e4caeb9850c5eb89571d31d51d4e8fb4.png\" width=\"1560\" height=\"1164\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/e4c\/aeb\/985\/e4caeb9850c5eb89571d31d51d4e8fb4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e4c\/aeb\/985\/e4caeb9850c5eb89571d31d51d4e8fb4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u0428\u0430\u0433 4: \u043f\u043b\u0430\u0432\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u043c\u0435\u0436\u0434\u0443 \u0442\u0435\u043c\u0430\u043c\u0438<\/h3>\n<p>\u041c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0432 \u0448\u0435\u0439\u0434\u0435\u0440 \u0434\u0432\u0430 \u043d\u0430\u0431\u043e\u0440\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 (\u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439) \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>transitionProgress<\/code> \u043e\u0442 0 \u0434\u043e 1:<\/p>\n<pre><code class=\"swift\">var themeTransitionProgress: Float {    let elapsed = CACurrentMediaTime() - transitionStartTime    let progress = min(max(elapsed \/ 0.7, 0), 1)    return Float(1 - pow(1 - progress, 3))  \/\/ ease-out cubic}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code>float3 colorA = renderTheme(params_current,  uv, u);float3 colorB = renderTheme(params_previous, uv, u);float3 color  = mix(colorB, colorA, u.transitionProgress);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0440\u043e\u0441\u0441-\u0444\u0435\u0439\u0434 0.7 \u0441\u0435\u043a\u0443\u043d\u0434\u044b \u0441 \u043a\u0443\u0431\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u0440\u0438\u0432\u043e\u0439 \u0437\u0430\u043c\u0435\u0434\u043b\u0435\u043d\u0438\u044f. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043c\u0435\u0436\u0434\u0443 \u043b\u044e\u0431\u044b\u043c\u0438 \u0434\u0432\u0443\u043c\u044f \u0442\u0435\u043c\u0430\u043c\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u043c\u0435\u0436\u0434\u0443 family (\u043f\u043b\u0430\u0437\u043c\u0430 \u2192 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u044b).<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 5: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u043e\u0432 \u0438 \u0431\u0430\u0433 \u0441 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0435\u0439<\/h3>\n<p>\u041f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \/ \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430 macOS \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 <code>NSApplication.didChangeScreenParametersNotification<\/code>:<\/p>\n<pre><code class=\"swift\">@objc private func handleScreenConfigurationChange() {    \/\/ \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0443\u0436\u043d\u0430 \u2014 \u0431\u0435\u0437 \u043d\u0435\u0451 NSScreen.screens \u0435\u0449\u0451 \u043d\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in        self?.refreshDisplaysAndWallpaperWindows()    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u2014 \u0432\u0440\u0435\u043c\u044f \u0441\u0442\u0430\u0440\u0442\u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u044d\u043f\u043e\u0445\u0430 \u041c\u0430\u043d\u0434\u0435\u043b\u044c\u0431\u0440\u043e\u0442\u0430 \u0438 \u0442.\u043f. \u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e: \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c hotplug \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441\u043d\u0430\u0447\u0430\u043b\u0430.<\/p>\n<p>\u041f\u043e\u0445\u043e\u0436\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432\u044b\u043b\u0435\u0437\u043b\u0430 \u0441\u043e Spaces. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 <code>collectionBehavior<\/code> \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0430\u043b <code>.stationary<\/code>, \u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0431\u043e\u0447\u0438\u043c\u0438 \u0441\u0442\u043e\u043b\u0430\u043c\u0438 \u043e\u043a\u043d\u0430 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0438\u0441\u044c \u0437\u0430\u043d\u043e\u0432\u043e \u2014 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0432\u0438\u0442\u0447. \u0424\u0438\u043a\u0441 \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043d\u043e \u0441\u0438\u043c\u043f\u0442\u043e\u043c \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439: \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u201c\u043e\u0431\u043e\u0438 \u043c\u0438\u0433\u0430\u044e\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Space\u201d.<\/p>\n<p>\u041d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440. \u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0441\u0430\u043c \u0441\u043e\u0437\u0434\u0430\u0451\u0442 command queue \u0438 pipeline.<\/p>\n<p>\u0415\u0441\u0442\u044c \u043d\u0435\u0440\u0435\u0448\u0451\u043d\u043d\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0445 \u0434\u0432\u0443\u0445 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430\u0445, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c Space \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c, \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u043d\u043e \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442\u044c \u2014 FPS \u043f\u0430\u0434\u0430\u0435\u0442, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0434\u0451\u0440\u0433\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u0445\u043e\u0436\u0435, macOS \u0441\u043d\u0438\u0436\u0430\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 render loop \u0434\u043b\u044f desktop-layer \u043e\u043a\u043e\u043d \u043d\u0430 \u0434\u0438\u0441\u043f\u043b\u0435\u044f\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0432\u043e\u0432\u043b\u0435\u0447\u0435\u043d\u044b \u0432 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 Space-\u043f\u0435\u0440\u0435\u0445\u043e\u0434. Workaround \u043f\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0448\u0451\u043b \u2014 \u044d\u0442\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u0430 \u043d\u0435 \u0431\u0430\u0433 \u0432 \u043a\u043e\u0434\u0435 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0449\u0435, \u043d\u043e \u043d\u0435 \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u043e: <code>MTLDevice<\/code> \u0438 pipeline state \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0431\u0449\u0438\u0439 <code>MetalContext<\/code>, \u0430 \u043d\u0430 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u2014 command queue, uniforms, \u0442\u0430\u0439\u043c\u0438\u043d\u0433\u0438.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 6: \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 SwiftUI UI<\/h3>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/af3\/93c\/145\/af393c14585e6c9400ac9d4b31f6d918.png\" alt=\"\u041f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 Control Center \u0441 \u0436\u0438\u0432\u044b\u043c \u043f\u0440\u0435\u0432\u044c\u044e\" title=\"\u041f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 Control Center \u0441 \u0436\u0438\u0432\u044b\u043c \u043f\u0440\u0435\u0432\u044c\u044e\" width=\"1560\" height=\"1164\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/af3\/93c\/145\/af393c14585e6c9400ac9d4b31f6d918.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/af3\/93c\/145\/af393c14585e6c9400ac9d4b31f6d918.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 Control Center \u0441 \u0436\u0438\u0432\u044b\u043c \u043f\u0440\u0435\u0432\u044c\u044e<\/figcaption><\/div>\n<\/figure>\n<p>AppKit \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043e\u043a\u043e\u043d, SwiftUI \u2014 \u0437\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, Metal \u2014 \u0437\u0430 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440. \u0414\u043b\u044f \u043f\u0430\u043d\u0435\u043b\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 <code>NavigationSplitView<\/code> \u0441 \u0431\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0430\u043d\u0435\u043b\u044c\u044e \u0438 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0434\u0435\u0442\u0430\u043b\u0435\u0439. \u0421\u0442\u0435\u0439\u0442 \u0432 <code>WallpaperSettingsStore<\/code> \u2014 <code>ObservableObject<\/code>, \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 <code>UserDefaults<\/code> \u0447\u0435\u0440\u0435\u0437 JSON.<\/p>\n<p>\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u0442\u0435\u043c\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u2014 \u044d\u0442\u043e <code>NSViewRepresentable<\/code> \u0441 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u043c <code>MTKView<\/code> \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u201c\u0431\u043e\u0435\u0432\u044b\u0445\u201d \u043e\u043a\u043e\u043d \u043d\u0430 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u0442\u043e\u043b\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fa6\/42e\/db4\/fa642edb48c58a22f935b6959a14a0e7.png\" alt=\"Theme Gallery \u2014 \u0436\u0438\u0432\u044b\u0435 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440\u044b \u0432\u0441\u0435\u0445 19 \u0442\u0435\u043c\" title=\"Theme Gallery \u2014 \u0436\u0438\u0432\u044b\u0435 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440\u044b \u0432\u0441\u0435\u0445 19 \u0442\u0435\u043c\" width=\"1560\" height=\"1150\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/fa6\/42e\/db4\/fa642edb48c58a22f935b6959a14a0e7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fa6\/42e\/db4\/fa642edb48c58a22f935b6959a14a0e7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Theme Gallery \u2014 \u0436\u0438\u0432\u044b\u0435 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440\u044b \u0432\u0441\u0435\u0445 19 \u0442\u0435\u043c<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u0428\u0430\u0433 7: \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438 \u0432\u0445\u043e\u0434\u0435 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443<\/h3>\n<p>\u0412 macOS 13+ \u0435\u0441\u0442\u044c <code>SMAppService<\/code>:<\/p>\n<pre><code class=\"swift\">try SMAppService.mainApp.register()   \/\/ \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044ctry SMAppService.mainApp.unregister() \/\/ \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e <code>.app<\/code> bundle. \u0412 dev-\u0441\u0431\u043e\u0440\u043a\u0435 \u0447\u0435\u0440\u0435\u0437 <code>swift run<\/code> \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438. \u041f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 macOS 14 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 prompt \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u044f\u0432\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 8: \u043f\u0430\u0443\u0437\u0430 \u043f\u0440\u0438 Low Power Mode<\/h3>\n<pre><code class=\"swift\">NotificationCenter.default.addObserver(    self,    selector: #selector(handlePowerStateChange),    name: NSProcessInfo.powerStateDidChangeNotification,    object: ProcessInfo.processInfo)private func applyPowerPolicy() {    let shouldPause = preferences.pauseOnLowPowerMode        &amp;&amp; ProcessInfo.processInfo.isLowPowerModeEnabled    for renderer in renderers.values {        renderer.setPaused(shouldPause, reason: \"Low Power Mode\")    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>view.isPaused = true<\/code> \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 render loop: \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043a\u0430\u0434\u0440\u044b \u043d\u0430 GPU. \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0440\u0438 <code>willSleepNotification<\/code> \/ <code>screensDidSleepNotification<\/code>.<\/p>\n<hr\/>\n<h3>\u0428\u0430\u0433 9: \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u0432 .app \u0431\u0435\u0437 Xcode<\/h3>\n<p>SwiftPM \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 <code>.app<\/code> bundle \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u041d\u0443\u0436\u0435\u043d shell-\u0441\u043a\u0440\u0438\u043f\u0442:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bashset -euo pipefailAPP_NAME=\"NeonDrift\"BUNDLE_ID=\"com.yourname.neon-drift\"VERSION=\"0.1.0\"APP_DIR=\"$APP_NAME.app\/Contents\"swift build -c releasemkdir -p \"$APP_DIR\/MacOS\" \"$APP_DIR\/Resources\"cp \".build\/release\/$APP_NAME\" \"$APP_DIR\/MacOS\/\"cp -r \".build\/release\/${APP_NAME}_${APP_NAME}.bundle\" \"$APP_DIR\/Resources\/\"cat &gt; \"$APP_DIR\/Info.plist\" &lt;&lt; EOF&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST 1.0\/\/EN\"  \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt;&lt;plist version=\"1.0\"&gt;&lt;dict&gt;    &lt;key&gt;CFBundleExecutable&lt;\/key&gt;        &lt;string&gt;$APP_NAME&lt;\/string&gt;    &lt;key&gt;CFBundleIdentifier&lt;\/key&gt;        &lt;string&gt;$BUNDLE_ID&lt;\/string&gt;    &lt;key&gt;CFBundleShortVersionString&lt;\/key&gt; &lt;string&gt;$VERSION&lt;\/string&gt;    &lt;key&gt;LSMinimumSystemVersion&lt;\/key&gt;    &lt;string&gt;14.0&lt;\/string&gt;    &lt;key&gt;NSHighResolutionCapable&lt;\/key&gt;   &lt;true\/&gt;&lt;\/dict&gt;&lt;\/plist&gt;EOF<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>Bundle.module: \u043a\u0440\u044d\u0448 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e <code>.app<\/code> \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0441\u044f \u043a\u0440\u044d\u0448\u0435\u043c \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435. \u0412 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u2014 <code>assertionFailure<\/code> \u0438\u0437 \u043d\u0435\u0434\u0440 SPM. SPM-\u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 resource accessor \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043d\u0430\u0439\u0442\u0438 \u0431\u0430\u043d\u0434\u043b \u0440\u044f\u0434\u043e\u043c \u0441 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 <code>.build\/<\/code>. \u041f\u043e\u0441\u043b\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0431\u0430\u043d\u0434\u043b \u043b\u0435\u0436\u0438\u0442 \u0432 <code>Contents\/Resources\/<\/code> \u2014 accessor \u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442.<\/p>\n<p>\u041f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043b\u043e\u043a\u0430\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043e\u0431\u0430 \u043c\u0435\u0441\u0442\u0430:<\/p>\n<pre><code class=\"swift\">enum ShaderBundleLocator {    static var shaderDirectoryURL: URL? {        let bundleName = \"NeonDrift_NeonDrift.bundle\"        \/\/ \u0423\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0439 .app: Contents\/Resources\/        if let resourcesURL = Bundle.main.resourceURL {            let url = resourcesURL.appendingPathComponent(bundleName)            if let b = Bundle(url: url) { return b.resourceURL }        }        \/\/ SPM dev-\u0441\u0431\u043e\u0440\u043a\u0430: \u0440\u044f\u0434\u043e\u043c \u0441 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u043c        let url = Bundle.main.bundleURL.appendingPathComponent(bundleName)        if let b = Bundle(url: url) { return b.resourceURL }        return Bundle.main.resourceURL    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0438 <code>swift run<\/code>, \u0438 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <code>.app<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u043e.<\/p>\n<hr\/>\n<h3>\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/h3>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/182\/db7\/4db\/182db74db9c61567d1b671feb46fd927.png\" alt=\"Diagnostics \u2014 FPS, \u0441\u0442\u0430\u0442\u0443\u0441 \u0440\u0435\u043d\u0434\u0435\u0440\u0430, \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430\" title=\"Diagnostics \u2014 FPS, \u0441\u0442\u0430\u0442\u0443\u0441 \u0440\u0435\u043d\u0434\u0435\u0440\u0430, \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430\" width=\"1560\" height=\"1164\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/182\/db7\/4db\/182db74db9c61567d1b671feb46fd927.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/182\/db7\/4db\/182db74db9c61567d1b671feb46fd927.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Diagnostics \u2014 FPS, \u0441\u0442\u0430\u0442\u0443\u0441 \u0440\u0435\u043d\u0434\u0435\u0440\u0430, \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0417\u0430\u043c\u0435\u0440\u044f\u043b \u043d\u0430 MacBook Pro M4 Pro 24 GB, \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0434\u0438\u0441\u043f\u043b\u0435\u0439 1512\u00d7982 pt (Retina 2\u00d7, \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 3024\u00d71964 px), macOS Sequoia 15.4, \u043e\u0434\u0438\u043d \u043c\u043e\u043d\u0438\u0442\u043e\u0440, Activity Monitor \u2192 GPU History.<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th data-colwidth=\"246\" width=\"246\">\n<p align=\"left\">\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439<\/p>\n<\/th>\n<th>\n<p align=\"left\">GPU<\/p>\n<\/th>\n<th>\n<p align=\"left\">CPU<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td data-colwidth=\"246\" width=\"246\">\n<p align=\"left\">\u041f\u043b\u0430\u0437\u043c\u0430 \/ \u041f\u0430\u0442\u0442\u0435\u0440\u043d\u044b, 60 FPS<\/p>\n<\/td>\n<td>\n<p align=\"left\">~9-17%<\/p>\n<\/td>\n<td>\n<p align=\"left\">~8-15%<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"246\" width=\"246\">\n<p align=\"left\">\u0424\u0440\u0430\u043a\u0442\u0430\u043b\u044b (\u041c\u0430\u043d\u0434\u0435\u043b\u044c\u0431\u0440\u043e\u0442), 60 FPS<\/p>\n<\/td>\n<td>\n<p align=\"left\">~9\u201323%<\/p>\n<\/td>\n<td>\n<p align=\"left\">~8\u201317%<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"246\" width=\"246\">\n<p align=\"left\">\u041b\u044e\u0431\u0430\u044f \u0442\u0435\u043c\u0430, 30 FPS<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043d\u0430 2-3 \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430 \u043c\u0435\u043d\u044c\u0448\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043d\u0430 2-3 \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430 \u043c\u0435\u043d\u044c\u0448\u0435<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"246\" width=\"246\">\n<p align=\"left\">\u041f\u0430\u0443\u0437\u0430 (Low Power Mode)<\/p>\n<\/td>\n<td>\n<p align=\"left\">0%<\/p>\n<\/td>\n<td>\n<p align=\"left\">0%<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0424\u0440\u0430\u043a\u0442\u0430\u043b\u044b \u0442\u044f\u0436\u0435\u043b\u0435\u0435 \u043f\u043b\u0430\u0437\u043c\u044b \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u043d\u0430 \u043f\u0438\u043a\u0441\u0435\u043b\u044c. \u041d\u0430 \u0434\u0432\u0430 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0440\u0430\u0441\u0442\u0451\u0442 \u043f\u043e\u0447\u0442\u0438 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0441\u0443\u043c\u043c\u0430\u0440\u043d\u043e\u043c\u0443 \u0447\u0438\u0441\u043b\u0443 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0434\u0432\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 render loop. \u0426\u0438\u0444\u0440\u044b \u0441\u0438\u043b\u044c\u043d\u043e \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 thermal state: \u043f\u043e\u0434 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 MacBook \u043c\u043e\u0436\u0435\u0442 \u0442\u0440\u043e\u0442\u0442\u043b\u0438\u0442\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 GPU load \u0438 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c FPS \u0431\u0443\u0434\u0443\u0442 \u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f.<\/p>\n<hr\/>\n<h3>\u0427\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e (\u0438 \u043f\u043e\u0447\u0435\u043c\u0443)<\/h3>\n<p><strong>SceneKit \/ SpriteKit.<\/strong> \u041f\u0435\u0440\u0432\u0430\u044f \u043c\u044b\u0441\u043b\u044c \u0431\u044b\u043b\u0430 \u2014 \u0432\u0437\u044f\u0442\u044c SceneKit, \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c SCNPlane, \u043a\u0438\u043d\u0443\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0448\u0435\u0439\u0434\u0435\u0440. \u042f \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b \u0434\u0435\u043d\u044c \u043d\u0430 \u044d\u0442\u043e, \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u043f\u043e\u0442\u043e\u043c \u0432\u044b\u043a\u0438\u043d\u0443\u043b. \u041d\u0435 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e SceneKit \u043f\u043b\u043e\u0445\u043e\u0439 \u2014 \u0430 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043c\u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d fullscreen quad \u0438 \u043e\u0434\u0438\u043d render pass. SceneKit \u0442\u0430\u0449\u0438\u0442 \u0437\u0430 \u0441\u043e\u0431\u043e\u0439 \u0433\u0440\u0430\u0444 \u0441\u0446\u0435\u043d\u044b, \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432, \u0444\u0438\u0437\u0438\u043a\u0443. \u042d\u0442\u043e \u043a\u0430\u043a \u0435\u0445\u0430\u0442\u044c \u0437\u0430 \u0445\u043b\u0435\u0431\u043e\u043c \u043d\u0430 \u0433\u0440\u0443\u0437\u043e\u0432\u0438\u043a\u0435.<\/p>\n<p><strong>ScreenSaverView.<\/strong> \u0415\u0441\u0442\u044c ScreenSaver API: <code>ScreenSaverView<\/code>, <code>configureSheet<\/code>, \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 System Settings. \u042f \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b \u2014 \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u043a\u0430, \u043d\u0435 \u043a\u0430\u043a \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0444\u043e\u043d. ScreenSaver \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043b\u044e\u0431\u043e\u0439 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041d\u0435 \u0442\u043e.<\/p>\n<p><strong>Bundle.module.<\/strong> \u041e\u043f\u0438\u0441\u0430\u043d\u043e \u0432\u044b\u0448\u0435. \u0421\u0438\u043c\u043f\u0442\u043e\u043c \u043c\u0435\u0440\u0437\u043a\u0438\u0439 \u2014 <code>assertionFailure<\/code> \u0431\u0435\u0437 \u0432\u043d\u044f\u0442\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0434\u0440\u0435\u0441 \u0432 \u0441\u0442\u0435\u043a\u0435. \u042f \u043c\u0438\u043d\u0443\u0442 20 \u0434\u0443\u043c\u0430\u043b \u0447\u0442\u043e \u0441\u043b\u043e\u043c\u0430\u043b \u043b\u0438\u043d\u043a\u043e\u0432\u043a\u0443.<\/p>\n<p><strong>App Store.<\/strong> \u041f\u0440\u043e\u0431\u043e\u0432\u0430\u043b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0441\u0431\u043e\u0440\u043a\u0443 \u0434\u043b\u044f MAS. \u0412 \u043c\u043e\u0435\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 sandbox-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0441\u043b\u043e\u043c\u0430\u043b\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 desktop-layer \u043e\u043a\u043d\u0430: \u043e\u043d\u043e \u043b\u0438\u0431\u043e \u043d\u0435 \u0432\u0441\u0442\u0430\u0432\u0430\u043b\u043e \u043d\u0430 \u043d\u0443\u0436\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c, \u043b\u0438\u0431\u043e \u0432\u0435\u043b\u043e \u0441\u0435\u0431\u044f \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u044d\u0442\u043e \u0440\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 entitlements \u0438\u043b\u0438 <code>collectionBehavior<\/code> \u2014 \u044f \u043d\u0435 \u0441\u0442\u0430\u043b \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u044d\u0442\u043e \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u043a\u0430 \u043e\u0441\u0442\u0430\u0432\u0438\u043b \u043f\u0440\u044f\u043c\u0443\u044e \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0446\u0438\u044e.<\/p>\n<hr\/>\n<h3>\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c: \u0447\u0442\u043e \u044f \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b<\/h3>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">macOS 14, \u043e\u0434\u0438\u043d \u043c\u043e\u043d\u0438\u0442\u043e\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">macOS 15, \u043e\u0434\u0438\u043d \u043c\u043e\u043d\u0438\u0442\u043e\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">\u0414\u0432\u0430 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430, hotplug<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">\u0414\u0432\u0430 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430, \u0441\u043c\u0435\u043d\u0430 Space \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0424\u0440\u0438\u0437\u044b \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0435 \u2014 \u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u043e<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">Mission Control<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u043a\u043d\u0430 \u043d\u0435 \u0432\u0438\u0434\u043d\u044b \u2014 \u043a\u0430\u043a \u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 Spaces<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043d\u0435 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">Full Screen app \u2192 \u0432\u044b\u0445\u043e\u0434<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0418\u043d\u043e\u0433\u0434\u0430 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442 \u043f\u043e\u0440\u044f\u0434\u043a\u0430 \u043e\u043a\u043e\u043d \u043d\u0430 ~0.3 \u0441\u0435\u043a<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">Stage Manager \u0432\u043a\u043b\u044e\u0447\u0451\u043d<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e \u043d\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u0432\u0441\u0435\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">Sleep \u2192 Wake<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u043a\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"363\" width=\"363\">\n<p align=\"left\">Low Power Mode<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0435\u043d\u0434\u0435\u0440 \u043f\u0430\u0443\u0437\u0438\u0442\u0441\u044f, \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Stage Manager \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0431\u0430\u0437\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f\u0445: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043e\u043a\u043e\u043d, Mission Control \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0438\u0437 Full Screen. \u0421\u043b\u043e\u0436\u043d\u044b\u0435 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u0438 \u2014 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u043f\u043b\u0435\u0435\u0432 \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 Space \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u2014 \u044f \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u043b.<\/p>\n<hr\/>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0446\u0435\u043b\u0438\u043a\u043e\u043c<\/h3>\n<pre><code>AppDelegate\u251c\u2500\u2500 refreshDisplaysAndWallpaperWindows()   \u2014 \u043e\u043a\u043d\u043e \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 NSScreen\u251c\u2500\u2500 PlasmaRenderer (MTKViewDelegate)       \u2014 \u043e\u0434\u0438\u043d \u043d\u0430 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u2502   \u251c\u2500\u2500 init(view:)                        \u2014 device, commandQueue, pipeline\u2502   \u251c\u2500\u2500 loadShaderSource()                 \u2014 .metal \u0438\u0437 \u0431\u0430\u043d\u0434\u043b\u0430 \u2192 \u0441\u0442\u0440\u043e\u043a\u0430\u2502   \u251c\u2500\u2500 draw(in:)                          \u2014 uniforms \u2192 encoder \u2192 present\u2502   \u251c\u2500\u2500 mtkView(_:drawableSizeWillChange:) \u2014 resize\/Retina\/hotplug\u2502   \u2514\u2500\u2500 apply(configuration:)             \u2014 \u0442\u0435\u043c\u0430 + transition\u251c\u2500\u2500 WallpaperSettingsStore (ObservableObject)\u2502   \u251c\u2500\u2500 UserDefaults (JSON)                \u2014 \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c\u2502   \u251c\u2500\u2500 SMAppService                       \u2014 login item\u2502   \u2514\u2500\u2500 callbacks \u2192 AppDelegate\u2514\u2500\u2500 WallpaperSettingsView (SwiftUI)    \u251c\u2500\u2500 NavigationSplitView    \u251c\u2500\u2500 WallpaperPreviewView (NSViewRepresentable + MTKView)    \u2514\u2500\u2500 ConfigurationEditorCard<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>Production-\u043d\u044e\u0430\u043d\u0441\u044b<\/h3>\n<ul>\n<li>\n<p><code>drawableSize<\/code>, \u043d\u0435 <code>bounds<\/code> \u2014 \u0438\u043d\u0430\u0447\u0435 \u043d\u0430 Retina \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u0432 \u043b\u0435\u0432\u043e\u043c \u043d\u0438\u0436\u043d\u0435\u043c \u0443\u0433\u043b\u0443.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0439\u0442\u0435 <code>mtkView(_:drawableSizeWillChange:)<\/code>: \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u0440\u0430, \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u0431\u0443\u0434\u0443\u0449\u0435\u0439 resize\/Retina\/hotplug-\u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>FPS configurable: 30\/60\/120. 120 \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0434\u0438\u0441\u043f\u043b\u0435\u044f\u0445 \u0441 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u043e\u0442\u043e\u0439 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f; \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 60 Hz \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043b\u0438\u0448\u043d\u044f\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0431\u0435\u0437 \u0432\u0438\u0434\u0438\u043c\u043e\u0433\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0430\u0443\u0437\u0438\u0442\u044c \u043f\u0440\u0438 Low Power Mode, sleep, \u0438 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u2014 \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 sleep <code>currentDrawable<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c <code>nil<\/code> \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u0434\u0440\u043e\u0432 \u2014 guard \u0432 <code>draw(in:)<\/code> \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d.<\/p>\n<\/li>\n<li>\n<p><code>MTLDevice<\/code> \u0438 pipeline state \u043c\u043e\u0436\u043d\u043e \u0448\u0430\u0440\u0438\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u0430\u043c\u0438 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440 \u044d\u0442\u043e \u043b\u0438\u0448\u043d\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b (\u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0435 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043e).<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 \u043d\u0430 macOS \u2014 \u044d\u0442\u043e \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c window-level hack: <code>NSWindow<\/code> \u0441 \u0443\u0440\u043e\u0432\u043d\u0435\u043c <code>CGWindowLevelForKey(.desktopWindow) + 1<\/code>, <code>ignoresMouseEvents<\/code>, \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 <code>collectionBehavior<\/code>. \u0414\u0430\u043b\u044c\u0448\u0435 \u2014 Metal render loop \u043f\u043e\u0432\u0435\u0440\u0445 <code>MTKView<\/code>, \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u044b\u0439 \u0448\u0435\u0439\u0434\u0435\u0440 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0446\u0432\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u0438\u0437 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0438, \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e AppKit-\u043a\u043b\u0435\u044f \u0434\u043b\u044f \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u043d\u0430 \u0441\u043c\u0435\u043d\u0443 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u043e\u0432, sleep\/wake \u0438 Low Power Mode.<\/p>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u2014 \u043e\u043a\u043e\u043b\u043e 2600 \u0441\u0442\u0440\u043e\u043a Swift \u0438 ~1200 \u0441\u0442\u0440\u043e\u043a Metal. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439. macOS 14+ \u2014 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u043d\u0435 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430.<\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438: <a href=\"https:\/\/github.com\/maxches99\/NeonDrift\" rel=\"noopener noreferrer nofollow\">github.com\/maxches99\/NeonDrift<\/a><\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1041702\/\">https:\/\/habr.com\/ru\/articles\/1041702\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u042f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 NeonDrift \u2014 \u0436\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 \u0434\u043b\u044f macOS \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 Metal-\u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432. \u0414\u043b\u044f \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, Screen Recording \u0438\u043b\u0438 Accessibility-\u0434\u043e\u0441\u0442\u0443\u043f. \u0422\u043e\u043b\u044c\u043a\u043e AppKit, MetalKit \u0438 SwiftUI.\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0443 \u043a\u0430\u043a \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u043e\u0442 \u0442\u0440\u044e\u043a\u0430 \u0441 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 \u043e\u043a\u043e\u043d \u0434\u043e \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 \u0438 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0432 .app. \u041f\u043e\u043f\u0443\u0442\u043d\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0431\u0430\u0433\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043f\u043e\u0439\u043c\u0430\u043b \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u2014 \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u0443\u044e \u043f\u043b\u0430\u0437\u043c\u0443 \u043d\u0430 Retina, \u043a\u0440\u044d\u0448 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Space, \u0438 \u0444\u0440\u0438\u0437\u044b \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0435 \u043f\u0440\u0438 \u0441\u043c\u0435\u043d\u0435 Space \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c.\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0435 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d wallpaper app, \u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430 macOS \u043c\u043e\u0436\u043d\u043e \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u0442\u044c AppKit window management, Metal render loop \u0438 SwiftUI-\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 API \u2014 \u0438 \u0433\u0434\u0435 \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0442\u0440\u0435\u0449\u0430\u0442\u044c \u043f\u043e \u0448\u0432\u0430\u043c.\u0416\u0438\u0432\u044b\u0435 \u043e\u0431\u043e\u0438 NeonDrift \u043d\u0430 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u0442\u043e\u043b\u0435\u0418\u0434\u0435\u044f: \u043d\u0435 \u043c\u0435\u043d\u044f\u0442\u044c \u043e\u0431\u043e\u0438, \u0430 \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0435\u0440\u0445 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430macOS \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e API \u0434\u043b\u044f \u0436\u0438\u0432\u044b\u0445 \u043e\u0431\u043e\u0435\u0432. \u041d\u043e \u0435\u0441\u0442\u044c \u043e\u0431\u0445\u043e\u0434\u043d\u043e\u0439 \u043f\u0443\u0442\u044c: \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u043e\u0435 NSWindow \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0440\u044f\u0434\u043e\u043c \u0441 desktop layer \u2014 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e \u043a\u0430\u043a \u0444\u043e\u043d: \u043d\u0435 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u043b\u043e \u043a\u043b\u0438\u043a\u0438, \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u043b\u043e\u0441\u044c \u0432 Mission Control \u0438 \u043d\u0435 \u043a\u043e\u043d\u043a\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u043e \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c\u0438 \u043e\u043a\u043d\u0430\u043c\u0438.\u042d\u0442\u043e \u043d\u0435 exploit: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API \u0443\u0440\u043e\u0432\u043d\u0435\u0439 \u043e\u043a\u043e\u043d \u2014 CGWindowLevelForKey(.desktopWindow). \u041d\u043e \u044d\u0442\u043e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e window-level hack, \u0430 \u043d\u0435 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 wallpaper API. \u0415\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 macOS \u0438 \u0440\u0435\u0436\u0438\u043c\u044b \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430: Stage Manager, Spaces, Full Screen \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u044f \u0438\u043d\u0430\u0447\u0435.\u0428\u0430\u0433 1: \u043e\u043a\u043d\u043e \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430\u0412\u043e\u0442 \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u201c\u043e\u0431\u043e\u0439\u043d\u043e\u0433\u043e\u201d \u043e\u043a\u043d\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430:let window = NSWindow(    contentRect: screen.frame,    styleMask: [.borderless],    backing: .buffered,    defer: false,    screen: screen)window.backgroundColor = .blackwindow.isOpaque = truewindow.hasShadow = falsewindow.animationBehavior = .nonewindow.isReleasedWhenClosed = false\/\/ \u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0438\u0442 \u0432\u0441\u0435 \u043a\u043b\u0438\u043a\u0438 \u043f\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u043b\u0443window.ignoresMouseEvents = true\/\/ \u041f\u0440\u0438\u043b\u0438\u043f\u0430\u0435\u0442 \u043a\u043e \u0432\u0441\u0435\u043c Space, \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Mission Control\/Expos\u00e9window.collectionBehavior = [    .canJoinAllSpaces,    .stationary,    .ignoresCycle,    .fullScreenAuxiliary  \/\/ \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u0432\/\u0438\u0437 Full Screen]\/\/ \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u043a\u043d\u043e \u043d\u0430\u0434 desktop layer, \u043d\u043e \u043d\u0438\u0436\u0435 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043e\u043a\u043e\u043dwindow.level = NSWindow.Level(    rawValue: Int(CGWindowLevelForKey(.desktopWindow)) + 1)window.setFrame(screen.frame, display: true)\/\/ \u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c \u043e\u043a\u043d\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e level \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043e\u043a\u043d\u0443.window.order(.above, relativeTo: 0)\u0414\u0432\u0430 \u043c\u043e\u043c\u0435\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u0430\u0436\u0443\u0442\u0441\u044f \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u043c\u0438, \u043d\u043e \u0431\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442:ignoresMouseEvents = true \u2014 \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043a\u043b\u0438\u043a\u0438 \u043f\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u043b\u0443. \u042f \u0437\u0430\u0431\u044b\u043b \u044d\u0442\u043e \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043f\u0440\u043e\u0432\u0451\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442 \u0432 \u043d\u0435\u0434\u043e\u0443\u043c\u0435\u043d\u0438\u0438, \u043f\u043e\u0447\u0435\u043c\u0443 \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0430\u043f\u043a\u0438..fullScreenAuxiliary \u0432 collectionBehavior \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043e\u043a\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u0438\u043b\u0438 \u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u044f \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u0432\/\u0438\u0437 Full Screen spaces. \u041e\u043d\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442, \u043d\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0435\u0439: \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0435 \u0438\u0437 full screen \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u0435\u0440\u0441\u0438\u0438 macOS.\u0428\u0430\u0433 2: Metal pipeline \u0438 render loop\u0414\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u0435\u043d Metal. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, command queue \u0438 pipeline:guard let device = MTLCreateSystemDefaultDevice() else {    throw RuntimeError(&#171;Metal \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 \u044d\u0442\u043e\u043c Mac.&#187;)}guard let commandQueue = device.makeCommandQueue() else {    throw RuntimeError(&#171;\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c command queue.&#187;)}\/\/ \u0428\u0435\u0439\u0434\u0435\u0440\u044b \u0433\u0440\u0443\u0437\u044f\u0442\u0441\u044f \u0438\u0437 .metal \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0431\u0430\u043d\u0434\u043b\u0435 \u043a\u0430\u043a \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430,\/\/ \u0430 \u043d\u0435 \u0438\u0437 default library \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e default library \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f\/\/ \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u0431\u043e\u0440\u043a\u0438, \u0430 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0448\u0435\u0439\u0434\u0435\u0440\u044b \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432let source = try loadShaderSource()let library = try device.makeLibrary(source: source, options: nil)let descriptor = MTLRenderPipelineDescriptor()descriptor.vertexFunction   = library.makeFunction(name: &#171;vs_main&#187;)descriptor.fragmentFunction = library.makeFunction(name: &#171;fs_main&#187;)descriptor.colorAttachments[0].pixelFormat = .bgra8Unormlet pipelineState = try device.makeRenderPipelineState(descriptor: descriptor)MTKView \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 device, \u043a\u043b\u0430\u0434\u0451\u0442\u0441\u044f \u0432 window \u043a\u0430\u043a contentView, \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0443 \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0443:let view = MTKView(frame: NSRect(origin: .zero, size: screen.frame.size))view.device = deviceview.colorPixelFormat = .bgra8Unormview.framebufferOnly = trueview.isPaused = falseview.enableSetNeedsDisplay = falseview.preferredFramesPerSecond = 60window.contentView = view\u0420\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 MTKViewDelegate. \u041f\u043e\u043c\u0438\u043c\u043e draw(in:) \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c mtkView(_:drawableSizeWillChange:) \u2014 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f lifecycle-\u0442\u043e\u0447\u043a\u0430 \u0434\u043b\u044f resize, Retina \u0438 hotplug:func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {    \/\/ \u0422\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0441\u0447\u0451\u0442\u0430 size-dependent \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432.    \/\/ \u0423 \u043d\u0430\u0441 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u2014 resolution \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f    \/\/ \u0447\u0435\u0440\u0435\u0437 uniforms \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0430\u0434\u0440. \u041d\u043e \u043c\u0435\u0442\u043e\u0434 \u043d\u0443\u0436\u0435\u043d \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e lifecycle.}\u0412\u0435\u0441\u044c \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 draw(in:):func draw(in view: MTKView) {    guard        let descriptor = view.currentRenderPassDescriptor,        let drawable   = view.currentDrawable,        let buffer     = commandQueue.makeCommandBuffer(),        let encoder    = buffer.makeRenderCommandEncoder(descriptor: descriptor)    else { return }    var uniforms = Uniforms(        time:       Float(CACurrentMediaTime() &#8212; startTime) * animationSpeed,        resolution: SIMD2(Float(view.drawableSize.width),                          Float(view.drawableSize.height)),        \/\/ &#8230; \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0442\u0435\u043c\u044b    )    encoder.setRenderPipelineState(pipelineState)    encoder.setFragmentBytes(&amp;uniforms, length: MemoryLayout&lt;Uniforms&gt;.stride, index: 0)    encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)    encoder.endEncoding()    buffer.present(drawable)    buffer.commit()}\u041f\u0440\u043e drawableSize vs bounds: \u043f\u0435\u0440\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0448\u0435\u0439\u0434\u0435\u0440\u0430 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0441 view.bounds.size \u2014 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u0443\u044e \u043f\u043b\u0430\u0437\u043c\u0443 \u043d\u0430 Retina. bounds \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0432 points, \u0430 in.position.xy \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u043c \u0448\u0435\u0439\u0434\u0435\u0440\u0435 \u2014 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438. \u041d\u0430 Retina-\u0434\u0438\u0441\u043f\u043b\u0435\u0435 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 2\u00d7, \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0441\u0436\u0438\u043c\u0430\u043b\u0430\u0441\u044c \u0432 \u043b\u0435\u0432\u044b\u0439 \u043d\u0438\u0436\u043d\u0438\u0439 \u0443\u0433\u043e\u043b \u0438 \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u043b\u0430\u0441\u044c \u043f\u043e viewport. view.drawableSize \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438 \u2014 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043c\u0435\u043d\u044b \u0432\u0441\u0451 \u0432\u0441\u0442\u0430\u043b\u043e \u043d\u0430 \u043c\u0435\u0441\u0442\u043e.\u041f\u0440\u043e setFragmentBytes: \u0443\u0434\u043e\u0431\u0435\u043d \u0434\u043b\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e uniforms-\u0431\u043b\u043e\u043a\u0430 (\u0443 \u043d\u0430\u0441 ~120 \u0431\u0430\u0439\u0442). \u0415\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0430\u0441\u0441\u0438\u0432\u044b \u0438\u043b\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u2014 \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 MTLBuffer.\u0428\u0430\u0433 3: \u0448\u0435\u0439\u0434\u0435\u0440 \u2014 \u0432\u0441\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u0412\u0435\u0440\u0442\u0435\u043a\u0441\u043d\u044b\u0439 \u0448\u0435\u0439\u0434\u0435\u0440 \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u0435\u043d \u2014 \u043e\u0434\u0438\u043d \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a \u043d\u0430 \u0432\u0435\u0441\u044c \u044d\u043a\u0440\u0430\u043d:vertex VertexOut vs_main(uint vertexID [[vertex_id]]) {    float2 positions[3] = {        float2(-1.0, -1.0),        float2( 3.0, -1.0),        float2(-1.0,  3.0),    };    VertexOut out;    out.position = float4(positions[vertexID], 0.0, 1.0);    return out;}\u0412\u0441\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u2014 \u0432\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043d\u043e\u043c \u0448\u0435\u0439\u0434\u0435\u0440\u0435. \u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u043b\u0430\u0437\u043c\u044b:fragment float4 fs_main(VertexOut in [[stage_in]],                         constant Uniforms &amp;u [[buffer(0)]]) {    \/\/ in.position.xy \u2014 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438, u.resolution \u2014 \u0442\u043e\u0436\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438    float2 uv = in.position.xy \/ u.resolution;    float2 p  = uv * 2.0 &#8212; 1.0;    p.x *= u.resolution.x \/ u.resolution.y;    float t = u.time * 0.4;    float v = sin(p.x * 3.0 + t)            + sin(p.y * 2.5 &#8212; t * 0.7)            + sin((p.x + p.y) * 2.0 + t * 1.3)            + sin(length(p) * 4.0 &#8212; t * 2.0);    v = v * 0.25 + 0.5;    return float4(palette(v, u.palettePreset), 1.0);}\u042d\u0442\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0434\u043b\u044f \u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u043d\u043e\u0439 \u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u2014 \u0442\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d Shadertoy. \u0412\u043c\u0435\u0441\u0442\u043e \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0438 \u0440\u0438\u0441\u0443\u0435\u043c \u043e\u0434\u0438\u043d \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0448\u0435\u0439\u0434\u0435\u0440 \u0441\u0430\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0446\u0432\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f.\u0428\u0430\u0433 4: \u043f\u043b\u0430\u0432\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u043c\u0435\u0436\u0434\u0443 \u0442\u0435\u043c\u0430\u043c\u0438\u041c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0432 \u0448\u0435\u0439\u0434\u0435\u0440 \u0434\u0432\u0430 \u043d\u0430\u0431\u043e\u0440\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 (\u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439) \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 transitionProgress \u043e\u0442 0 \u0434\u043e 1:var themeTransitionProgress: Float {    let elapsed = CACurrentMediaTime() &#8212; transitionStartTime    let progress = min(max(elapsed \/ 0.7, 0), 1)    return Float(1 &#8212; pow(1 &#8212; progress, 3))  \/\/ ease-out cubic}float3 colorA = renderTheme(params_current,  uv, u);float3 colorB = renderTheme(params_previous, uv, u);float3 color  = mix(colorB, colorA, u.transitionProgress);\u041a\u0440\u043e\u0441\u0441-\u0444\u0435\u0439\u0434 0.7 \u0441\u0435\u043a\u0443\u043d\u0434\u044b \u0441 \u043a\u0443\u0431\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u0440\u0438\u0432\u043e\u0439 \u0437\u0430\u043c\u0435\u0434\u043b\u0435\u043d\u0438\u044f. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043c\u0435\u0436\u0434\u0443 \u043b\u044e\u0431\u044b\u043c\u0438 \u0434\u0432\u0443\u043c\u044f \u0442\u0435\u043c\u0430\u043c\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u043c\u0435\u0436\u0434\u0443 family (\u043f\u043b\u0430\u0437\u043c\u0430 \u2192 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u044b).\u0428\u0430\u0433 5: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u043e\u0432 \u0438 \u0431\u0430\u0433 \u0441 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0435\u0439\u041f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \/ \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430 macOS \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 NSApplication.didChangeScreenParametersNotification:@objc private func handleScreenConfigurationChange() {    \/\/ \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0443\u0436\u043d\u0430 \u2014 \u0431\u0435\u0437 \u043d\u0435\u0451 NSScreen.screens \u0435\u0449\u0451 \u043d\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in        self?.refreshDisplaysAndWallpaperWindows()    }}\u041f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u2014 \u0432\u0440\u0435\u043c\u044f \u0441\u0442\u0430\u0440\u0442\u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u044d\u043f\u043e\u0445\u0430 \u041c\u0430\u043d\u0434\u0435\u043b\u044c\u0431\u0440\u043e\u0442\u0430 \u0438 \u0442.\u043f. \u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e: \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c hotplug \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441\u043d\u0430\u0447\u0430\u043b\u0430.\u041f\u043e\u0445\u043e\u0436\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432\u044b\u043b\u0435\u0437\u043b\u0430 \u0441\u043e Spaces. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 collectionBehavior \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0430\u043b .stationary, \u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0431\u043e\u0447\u0438\u043c\u0438 \u0441\u0442\u043e\u043b\u0430\u043c\u0438 \u043e\u043a\u043d\u0430 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0438\u0441\u044c \u0437\u0430\u043d\u043e\u0432\u043e \u2014 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0432\u0438\u0442\u0447. \u0424\u0438\u043a\u0441 \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043d\u043e \u0441\u0438\u043c\u043f\u0442\u043e\u043c \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439: \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u201c\u043e\u0431\u043e\u0438 \u043c\u0438\u0433\u0430\u044e\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Space\u201d.\u041d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440. \u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u0441\u0430\u043c \u0441\u043e\u0437\u0434\u0430\u0451\u0442 command queue \u0438 pipeline.\u0415\u0441\u0442\u044c \u043d\u0435\u0440\u0435\u0448\u0451\u043d\u043d\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0445 \u0434\u0432\u0443\u0445 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0430\u0445, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c Space \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c, \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u043d\u043e \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442\u044c \u2014 FPS \u043f\u0430\u0434\u0430\u0435\u0442, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0434\u0451\u0440\u0433\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u0445\u043e\u0436\u0435, macOS \u0441\u043d\u0438\u0436\u0430\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 render loop \u0434\u043b\u044f desktop-layer \u043e\u043a\u043e\u043d \u043d\u0430 \u0434\u0438\u0441\u043f\u043b\u0435\u044f\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0432\u043e\u0432\u043b\u0435\u0447\u0435\u043d\u044b \u0432 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 Space-\u043f\u0435\u0440\u0435\u0445\u043e\u0434. Workaround \u043f\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0448\u0451\u043b \u2014 \u044d\u0442\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u0430 \u043d\u0435 \u0431\u0430\u0433 \u0432 \u043a\u043e\u0434\u0435 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0449\u0435, \u043d\u043e \u043d\u0435 \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u043e: MTLDevice \u0438 pipeline state \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0431\u0449\u0438\u0439 MetalContext, \u0430 \u043d\u0430 \u0440\u0435\u043d\u0434\u0435\u0440\u0435\u0440 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u2014 command queue, uniforms, \u0442\u0430\u0439\u043c\u0438\u043d\u0433\u0438.\u0428\u0430\u0433 6: \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 SwiftUI UI\u041f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 Control Center \u0441 \u0436\u0438\u0432\u044b\u043c \u043f\u0440\u0435\u0432\u044c\u044eAppKit \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043e\u043a\u043e\u043d, SwiftUI \u2014 \u0437\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, Metal \u2014 \u0437\u0430 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0440\u0435\u043d\u0434\u0435\u0440. \u0414\u043b\u044f \u043f\u0430\u043d\u0435\u043b\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u2014 NavigationSplitView \u0441 \u0431\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0430\u043d\u0435\u043b\u044c\u044e \u0438 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0434\u0435\u0442\u0430\u043b\u0435\u0439. \u0421\u0442\u0435\u0439\u0442 \u0432 WallpaperSettingsStore \u2014 ObservableObject, \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 UserDefaults \u0447\u0435\u0440\u0435\u0437 JSON.\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u0442\u0435\u043c\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u2014 \u044d\u0442\u043e&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-481751","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=481751"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481751\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=481751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=481751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=481751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}