to drawRect hay not to drawRect (khi nào nên sử dụng drawRect / Core Graphics so với subview / hình ảnh và tại sao?)


142

Để làm rõ mục đích của câu hỏi này: Tôi biết CÁCH tạo các khung nhìn phức tạp với cả các cuộc phỏng vấn và sử dụng drawRect. Tôi đang cố gắng để hiểu đầy đủ khi nào và tại sao nên sử dụng cái này hơn cái kia.

Tôi cũng hiểu rằng sẽ không có ý nghĩa gì khi tối ưu hóa điều đó trước thời hạn và làm điều gì đó theo cách khó khăn hơn trước khi thực hiện bất kỳ hồ sơ nào. Hãy xem xét rằng tôi cảm thấy thoải mái với cả hai phương pháp và bây giờ thực sự muốn hiểu sâu hơn.

Rất nhiều sự nhầm lẫn của tôi đến từ việc học cách làm cho hiệu suất cuộn bảng xem thực sự trơn tru và nhanh chóng. Tất nhiên nguồn gốc của phương pháp này là từ tác giả đằng sau twitter cho iPhone (trước đây là tweetie). Về cơ bản nó nói rằng để làm cho việc cuộn bảng trở nên trơn tru, bí mật là KHÔNG sử dụng các cuộc phỏng vấn, mà thay vào đó hãy thực hiện tất cả các bản vẽ trong một uiview tùy chỉnh. Về cơ bản, có vẻ như việc sử dụng nhiều cuộc phỏng vấn sẽ làm chậm quá trình hiển thị vì chúng có rất nhiều chi phí và liên tục được tổng hợp lại theo quan điểm của phụ huynh.

Công bằng mà nói, điều này đã được viết khi 3GS là một thương hiệu mới, và iDevices đã nhanh hơn rất nhiều kể từ đó. Tuy nhiên , phương pháp này thường được đề xuất trên các interwebs và các nơi khác cho các bảng hiệu suất cao. Trên thực tế, đây là một phương pháp được đề xuất trong Mã mẫu của Apple , đã được đề xuất trong một số video WWDC ( Bản vẽ thực tế dành cho nhà phát triển iOS ) và nhiều sách lập trình iOS .

Thậm chí còn có các công cụ tìm kiếm tuyệt vời để thiết kế đồ họa và tạo mã Core Graphics cho chúng.

Vì vậy, lúc đầu tôi sẽ tin rằng "có một lý do tại sao Đồ họa lõi tồn tại. Nó NHANH CHÓNG!"

Nhưng ngay khi tôi nghĩ rằng tôi có ý tưởng "Ưu tiên đồ họa lõi khi có thể", tôi bắt đầu thấy drawRect thường chịu trách nhiệm cho khả năng phản hồi kém trong một ứng dụng, bộ nhớ cực kỳ tốn kém và thực sự đánh thuế CPU. Về cơ bản, tôi nên " Tránh ghi đè drawRect " ( Hiệu suất ứng dụng iOS WWDC 2012 : Đồ họa và hoạt hình )

Vì vậy, tôi đoán, giống như mọi thứ, nó phức tạp. Có lẽ bạn có thể giúp bản thân và những người khác hiểu khi nào và Tại sao lại sử dụng drawRect?

Tôi thấy một vài tình huống rõ ràng để sử dụng Đồ họa lõi:

  1. Bạn có dữ liệu động (ví dụ Biểu đồ chứng khoán của Apple)
  2. Bạn có một yếu tố UI linh hoạt không thể được thực thi với một hình ảnh có thể thay đổi kích thước đơn giản
  3. Bạn đang tạo một đồ họa động, một khi kết xuất được sử dụng ở nhiều nơi

Tôi thấy các tình huống để tránh Đồ họa lõi:

  1. Các thuộc tính của chế độ xem của bạn cần được làm động riêng
  2. Bạn có một hệ thống phân cấp chế độ xem tương đối nhỏ, do đó, bất kỳ nỗ lực bổ sung nào được sử dụng CG đều không đáng để đạt được
  3. Bạn muốn cập nhật các phần của chế độ xem mà không cần vẽ lại toàn bộ
  4. Bố cục của các cuộc phỏng vấn của bạn cần cập nhật khi kích thước khung nhìn cha mẹ thay đổi

Vì vậy, ban tặng kiến ​​thức của bạn. Trong những tình huống nào bạn tiếp cận với drawRect / Core Graphics (điều đó cũng có thể được thực hiện với các cuộc phỏng vấn)? Yếu tố nào dẫn bạn đến quyết định đó? Làm thế nào / Tại sao việc vẽ trong một chế độ xem tùy chỉnh được khuyến nghị cho việc cuộn tế bào bảng trơn tru, nhưng Apple khuyên drawRect chống lại vì lý do hiệu suất nói chung? Còn những hình nền đơn giản (khi nào bạn tạo chúng bằng CG so với sử dụng hình ảnh png có thể thay đổi kích thước) thì sao?

Một sự hiểu biết sâu sắc về chủ đề này có thể không cần thiết để tạo ra các ứng dụng đáng giá, nhưng tôi không thích lựa chọn giữa các kỹ thuật mà không thể giải thích lý do tại sao. Não tôi nổi giận với tôi.

Cập nhật câu hỏi

Cảm ơn thông tin của mọi người. Một số câu hỏi làm rõ ở đây:

  1. Nếu bạn đang vẽ một cái gì đó với đồ họa cốt lõi, nhưng có thể hoàn thành điều tương tự với UIImageViews và png được kết xuất sẵn, bạn có nên luôn đi theo con đường đó không?
  2. Một câu hỏi tương tự: Đặc biệt với các công cụ badass như thế này , khi nào bạn nên xem xét vẽ các yếu tố giao diện trong đồ họa cốt lõi? (Có lẽ khi màn hình của phần tử của bạn thay đổi. Ví dụ: một nút có 20 biến thể màu khác nhau. Có trường hợp nào khác không?)
  3. Theo sự hiểu biết của tôi trong câu trả lời của tôi dưới đây, liệu có thể đạt được hiệu suất tương tự cho một ô của bảng bằng cách chụp hiệu quả một ảnh chụp nhanh của ô của bạn sau khi hiển thị UIView phức tạp của bạn và hiển thị trong khi cuộn và ẩn chế độ xem phức tạp của bạn? Rõ ràng một số phần sẽ phải được làm việc ra. Chỉ là một suy nghĩ thú vị mà tôi có.

Câu trả lời:


72

Bám sát UIKit và phỏng vấn bất cứ khi nào bạn có thể. Bạn có thể làm việc hiệu quả hơn và tận dụng tất cả các cơ chế OO để mọi thứ dễ duy trì hơn. Sử dụng Đồ họa lõi khi bạn không thể đạt được hiệu suất bạn cần từ UIKit hoặc bạn biết việc cố gắng hack các hiệu ứng vẽ trong UIKit sẽ phức tạp hơn.

Quy trình làm việc chung nên được xây dựng các bảng xem với các cuộc phỏng vấn. Sử dụng Dụng cụ để đo tốc độ khung hình trên phần cứng cũ nhất mà ứng dụng của bạn sẽ hỗ trợ. Nếu bạn không thể nhận được 60 khung hình / giây, hãy thả xuống CoreGraphics. Khi bạn đã thực hiện điều này trong một thời gian, bạn sẽ có cảm giác khi UIKit có thể là một sự lãng phí thời gian.

Vậy, tại sao Core Graphics lại nhanh?

CoreGraphics không thực sự nhanh. Nếu nó được sử dụng mọi lúc, có lẽ bạn sẽ đi chậm. Đó là một API vẽ phong phú, đòi hỏi công việc của nó phải được thực hiện trên CPU, trái ngược với rất nhiều công việc UIKit được giảm tải cho GPU. Nếu bạn phải làm động một quả bóng di chuyển trên màn hình, sẽ là một ý tưởng khủng khiếp khi gọi setNeedDisplay trên chế độ xem 60 lần mỗi giây. Vì vậy, nếu bạn có các thành phần phụ của chế độ xem cần được tạo hình động riêng lẻ, mỗi thành phần phải là một lớp riêng biệt.

Vấn đề khác là khi bạn không thực hiện vẽ tùy chỉnh với drawRect, UIKit có thể tối ưu hóa chế độ xem chứng khoán để drawRect là không hoạt động hoặc có thể sử dụng phím tắt với kết hợp. Khi bạn ghi đè drawRect, UIKit phải đi theo con đường chậm vì nó không biết bạn đang làm gì.

Hai vấn đề này có thể vượt trội hơn bởi các lợi ích trong trường hợp các ô xem bảng. Sau khi drawRect được gọi khi lần đầu tiên xuất hiện trên màn hình, nội dung được lưu trong bộ nhớ cache và cuộn là một bản dịch đơn giản được thực hiện bởi GPU. Vì bạn đang xử lý một chế độ xem, thay vì phân cấp phức tạp, tối ưu hóa drawRect của UIKit trở nên ít quan trọng hơn. Vì vậy, nút cổ chai trở thành số lượng bạn có thể tối ưu hóa bản vẽ Core Graphics của mình.

Bất cứ khi nào bạn có thể, sử dụng UIKit. Thực hiện đơn giản nhất mà làm việc. Hồ sơ. Khi có một động lực, tối ưu hóa.


53

Sự khác biệt là về cơ bản UIView và CALayer xử lý các hình ảnh cố định. Những hình ảnh này được tải lên card đồ họa (nếu bạn biết OpenGL, hãy nghĩ về một hình ảnh như một kết cấu và UIView / CALayer là một đa giác hiển thị một kết cấu như vậy). Khi một hình ảnh nằm trên GPU, nó có thể được vẽ rất nhanh, thậm chí nhiều lần và (với một hình phạt hiệu suất nhẹ) ngay cả với các mức độ trong suốt alpha khác nhau trên các hình ảnh khác.

CoreGraphics / Quartz là một API để tạo hình ảnh. Nó lấy một bộ đệm pixel (một lần nữa, nghĩ rằng kết cấu OpenGL) và thay đổi các pixel riêng lẻ bên trong nó. Tất cả điều này xảy ra trong RAM và trên CPU, và chỉ khi Quartz hoàn thành, hình ảnh mới được "xóa" trở lại GPU. Chuyến đi khứ hồi này để lấy hình ảnh từ GPU, thay đổi nó, sau đó tải lên toàn bộ hình ảnh (hoặc ít nhất là một đoạn tương đối lớn của nó) trở lại GPU khá chậm. Ngoài ra, bản vẽ thực tế mà Quartz thực hiện, trong khi thực sự nhanh cho những gì bạn đang làm, chậm hơn so với những gì GPU làm.

Điều đó là hiển nhiên, xem xét GPU chủ yếu là di chuyển xung quanh các pixel không thay đổi trong khối lớn. Quartz truy cập ngẫu nhiên các pixel và chia sẻ CPU với mạng, âm thanh, v.v. Ngoài ra, nếu bạn có một số yếu tố bạn vẽ bằng Quartz cùng một lúc, bạn phải vẽ lại tất cả chúng khi thay đổi, sau đó tải lên toàn bộ khối, trong khi nếu bạn thay đổi một hình ảnh và sau đó để UIViews hoặc CALayers dán nó vào các hình ảnh khác của bạn, bạn có thể thoát khỏi việc tải lên một lượng dữ liệu nhỏ hơn nhiều vào GPU.

Khi bạn không triển khai -drawRect:, hầu hết các chế độ xem chỉ có thể được tối ưu hóa. Chúng không chứa bất kỳ pixel nào, vì vậy không thể vẽ bất cứ thứ gì. Các chế độ xem khác, như UIImageView, chỉ vẽ UIImage (một lần nữa, về cơ bản là tham chiếu đến một kết cấu, có lẽ đã được tải lên GPU). Vì vậy, nếu bạn vẽ cùng một UIImage 5 lần bằng UIImageView, nó chỉ được tải lên GPU một lần, sau đó được vẽ lên màn hình ở 5 vị trí khác nhau, giúp chúng ta tiết kiệm thời gian và CPU.

Khi bạn triển khai -drawRect:, điều này khiến hình ảnh mới được tạo. Sau đó, bạn vẽ vào CPU bằng Quartz. Nếu bạn vẽ UIImage trong drawRect của mình, nó có thể tải hình ảnh từ GPU, sao chép nó vào hình ảnh bạn đang vẽ và sau khi hoàn tất, hãy tải bản sao hình ảnh thứ hai này trở lại card đồ họa. Vì vậy, bạn đang sử dụng gấp đôi bộ nhớ GPU trên thiết bị.

Vì vậy, cách nhanh nhất để vẽ thường là tách nội dung tĩnh khỏi việc thay đổi nội dung (trong các lớp con UIViews / UIView / CALayers riêng biệt). Tải nội dung tĩnh dưới dạng UIImage và vẽ nó bằng UIImageView và đặt nội dung được tạo động khi chạy trong drawRect. Nếu bạn có nội dung được vẽ nhiều lần, nhưng bản thân nó không thay đổi (Ie 3 biểu tượng được hiển thị trong cùng một vị trí để biểu thị một số trạng thái) cũng sử dụng UIImageView.

Một cảnh báo: Có một điều như là có quá nhiều UIView. Các khu vực đặc biệt trong suốt sẽ thu phí lớn hơn trên GPU, bởi vì chúng cần được trộn lẫn với các pixel khác phía sau chúng khi được hiển thị. Đây là lý do tại sao bạn có thể đánh dấu một UIView là "mờ đục", để chỉ ra cho GPU rằng nó chỉ có thể xóa sạch mọi thứ đằng sau hình ảnh đó.

Nếu bạn có nội dung được tạo động khi chạy nhưng vẫn giữ nguyên trong suốt thời gian tồn tại của ứng dụng (ví dụ: nhãn chứa tên người dùng), thực sự có thể có ý nghĩa khi chỉ vẽ toàn bộ nội dung một lần bằng Quartz, với văn bản, nút viền, vv, như là một phần của nền. Nhưng đó thường là một tối ưu hóa không cần thiết trừ khi ứng dụng Dụng cụ cho bạn biết khác nhau.


Cảm ơn các câu trả lời chi tiết. Hy vọng nhiều người cuộn xuống và bỏ phiếu này là tốt.
Bob Spryn

Ước gì tôi có thể nâng cao điều này hai lần. Cảm ơn vì bài viết tuyệt vời!
Bờ biển Tây Tạng

Điều chỉnh nhẹ: Trong hầu hết các trường hợp không có tải xuống từ GPU, nhưng về cơ bản, tải lên vẫn là hoạt động chậm nhất mà bạn có thể yêu cầu GPU thực hiện, vì nó phải chuyển bộ đệm pixel lớn từ RAM hệ thống chậm hơn sang VRAM nhanh hơn. OTOH một khi hình ảnh ở đó, di chuyển nó chỉ cần gửi một bộ tọa độ mới (một vài byte) đến GPU để iy biết nơi để vẽ.
uliwitness

@uliwitness tôi đã xem qua câu trả lời của bạn và bạn đã đề cập đến việc đánh dấu một đối tượng là "mờ đục xóa sạch mọi thứ đằng sau hình ảnh đó". Bạn có thể vui lòng làm sáng tỏ phần đó?
Rikh

@Rikh Đánh dấu một lớp là mờ có nghĩa là a) trong hầu hết các trường hợp, GPU sẽ không bận tâm đến việc vẽ các lớp khác bên dưới lớp đó, ít nhất là các phần của chúng nằm dưới lớp mờ. Ngay cả khi chúng đã được vẽ, nó sẽ không thực sự pha trộn alpha, mà chỉ sao chép nội dung của lớp trên cùng trên màn hình (thường là các pixel trong suốt hoàn toàn trong một lớp mờ sẽ có màu đen hoặc ít nhất là phiên bản màu đục của chúng)
uliwitness

26

Tôi sẽ cố gắng và tóm tắt những gì tôi ngoại suy từ câu trả lời của người khác ở đây và đặt câu hỏi làm rõ trong bản cập nhật cho câu hỏi ban đầu. Nhưng tôi khuyến khích những người khác tiếp tục trả lời và bỏ phiếu cho những người đã cung cấp thông tin tốt.

Cách tiếp cận chung

Rõ ràng là cách tiếp cận chung, như Ben Sandofsky đã đề cập trong câu trả lời của mình , nên là "Bất cứ khi nào bạn có thể, hãy sử dụng UIKit. Thực hiện đơn giản nhất có hiệu quả. Hồ sơ. Khi có động lực, hãy tối ưu hóa."

Tại sao

  1. Có hai nút thắt chính có thể có trong iDevice, CPU và GPU
  2. CPU chịu trách nhiệm cho bản vẽ / kết xuất ban đầu của khung nhìn
  3. GPU chịu trách nhiệm cho phần lớn hoạt hình (Hoạt hình lõi), hiệu ứng lớp, tổng hợp, v.v.
  4. UIView có rất nhiều tối ưu hóa, bộ nhớ đệm, v.v., được tích hợp để xử lý phân cấp chế độ xem phức tạp
  5. Khi ghi đè drawRect, bạn bỏ lỡ rất nhiều lợi ích mà UIView cung cấp và nói chung là chậm hơn so với việc để UIView xử lý kết xuất.

Vẽ nội dung ô trong một UIView phẳng có thể cải thiện đáng kể FPS của bạn trên các bảng cuộn.

Giống như tôi đã nói ở trên, CPU và GPU là hai nút thắt có thể xảy ra. Vì họ thường xử lý những việc khác nhau, bạn phải chú ý đến nút thắt nào mà bạn đang gặp phải. Trong trường hợp cuộn bảng, không phải Core Graphics vẽ nhanh hơn và đó là lý do tại sao nó có thể cải thiện FPS của bạn rất nhiều.

Trên thực tế, Core Graphics rất có thể chậm hơn so với hệ thống phân cấp UIView lồng nhau cho kết xuất ban đầu. Tuy nhiên, có vẻ như lý do điển hình cho việc cuộn cuộn là bạn đang làm tắc nghẽn GPU, vì vậy bạn cần phải giải quyết điều đó.

Tại sao ghi đè drawRect (sử dụng đồ họa lõi) có thể giúp cuộn bảng:

Theo những gì tôi hiểu, GPU không chịu trách nhiệm cho việc hiển thị các chế độ xem ban đầu, mà thay vào đó là kết cấu bằng tay hoặc bitmap, đôi khi với một số thuộc tính lớp, sau khi chúng được hiển thị. Sau đó, nó chịu trách nhiệm tổng hợp các ảnh bitmap, hiển thị tất cả các lớp đó ảnh hưởng và phần lớn hoạt hình (Hoạt hình lõi).

Trong trường hợp các ô xem bảng, GPU có thể bị tắc nghẽn với hệ thống phân cấp chế độ xem phức tạp, vì thay vì hoạt hình một bitmap, nó đang tạo hiệu ứng cho chế độ xem cha mẹ và thực hiện các tính toán bố cục của khung nhìn phụ, hiển thị hiệu ứng lớp và tổng hợp tất cả các lần xem. Vì vậy, thay vì hoạt hình một bitmap, nó chịu trách nhiệm về mối quan hệ của bó bitmap và cách chúng tương tác với cùng một vùng pixel.

Vì vậy, tóm lại, lý do vẽ ô của bạn trong một chế độ xem bằng đồ họa lõi có thể tăng tốc độ cuộn bảng của bạn KHÔNG phải vì nó vẽ nhanh hơn, mà vì nó làm giảm tải cho GPU, đó là nút cổ chai gây rắc rối cho bạn trong kịch bản cụ thể đó .


1
Mặc dù cẩn thận. GPU tổng hợp lại hiệu quả toàn bộ cây lớp cho mỗi khung. Vì vậy, di chuyển một lớp có hiệu quả bằng không. Nếu bạn tạo một lớp lớn, chỉ di chuyển một lớp đó nhanh hơn di chuyển nhiều lớp. Di chuyển một cái gì đó bên trong lớp này đột nhiên liên quan đến CPU và có chi phí. Vì nội dung ô thường không thay đổi nhiều (chỉ đáp ứng với các hành động tương đối hiếm của người dùng), sử dụng ít lượt xem hơn sẽ tăng tốc mọi thứ. Nhưng tạo một chế độ xem lớn với tất cả các ô sẽ là một ý tưởng tồi ™.
uliwitness

6

Tôi là một nhà phát triển trò chơi và tôi đã hỏi những câu hỏi tương tự khi bạn tôi nói với tôi rằng hệ thống phân cấp chế độ xem dựa trên UIImageView của tôi sẽ làm chậm trò chơi của tôi và khiến nó trở nên khủng khiếp. Sau đó tôi đã tiến hành nghiên cứu mọi thứ tôi có thể tìm thấy về việc có nên sử dụng UIViews, CoreGraphics, OpenGL hoặc thứ gì đó của bên thứ 3 như Cocos2D hay không. Câu trả lời nhất quán mà tôi nhận được từ bạn bè, giáo viên và các kỹ sư của Apple tại WWDC là cuối cùng sẽ không có nhiều sự khác biệt bởi vì ở một mức độ nào đó, tất cả họ đều làm điều tương tự . Các tùy chọn cấp cao hơn như UIView dựa vào các tùy chọn cấp thấp hơn như CoreGraphics và OpenGL, chỉ cần chúng được bọc trong mã để giúp bạn dễ sử dụng hơn.

Đừng sử dụng CoreGraphics nếu bạn sắp viết lại UIView. Tuy nhiên, bạn có thể đạt được một số tốc độ từ việc sử dụng CoreGraphics, miễn là bạn thực hiện tất cả các bản vẽ của mình trong một chế độ xem, nhưng nó có thực sự đáng giá không? Câu trả lời tôi đã tìm thấy thường là không. Khi tôi mới bắt đầu trò chơi của mình, tôi đã làm việc với iPhone 3G. Khi trò chơi của tôi trở nên phức tạp, tôi bắt đầu thấy một số độ trễ, nhưng với các thiết bị mới hơn, nó hoàn toàn không được chú ý. Bây giờ tôi có rất nhiều hành động đang diễn ra và độ trễ duy nhất dường như là giảm 1-3 khung hình / giây khi chơi ở mức độ phức tạp nhất trên iPhone 4.

Tuy nhiên, tôi quyết định sử dụng Dụng cụ để tìm các chức năng chiếm nhiều thời gian nhất. Tôi thấy rằng các vấn đề không liên quan đến việc tôi sử dụng UIViews . Thay vào đó, nó đã liên tục gọi CGRectMake để tính toán cảm biến va chạm nhất định và tải các tệp hình ảnh và âm thanh riêng biệt cho các lớp nhất định sử dụng cùng một hình ảnh, thay vì chúng được vẽ từ một lớp lưu trữ trung tâm.

Vì vậy, cuối cùng, bạn có thể đạt được một chút lợi ích từ việc sử dụng CoreGraphics, nhưng thường thì nó sẽ không có giá trị hoặc có thể không có bất kỳ ảnh hưởng nào. Lần duy nhất tôi sử dụng CoreGraphics là khi vẽ các hình dạng hình học chứ không phải văn bản và hình ảnh.


8
Tôi nghĩ bạn có thể nhầm lẫn Core Animation với Core Graphics. Core Graphics thực sự chậm, vẽ dựa trên phần mềm và không được sử dụng để vẽ UIView thông thường trừ khi bạn ghi đè phương thức drawRect. Core Animation là phần cứng được tăng tốc, nhanh và chịu trách nhiệm cho hầu hết những gì bạn thấy trên màn hình.
Nick Lockwood
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.