2015-06-25 10 views
6

मैं एंड्रॉइड पर TextureView का उपयोग कर ड्राइंग/पेंटिंग ऐप बनाने का प्रयास कर रहा हूं। मैं 4096x4096 पिक्सेल तक की ड्राइंग सतह का समर्थन करना चाहता हूं, जो कि मेरे न्यूनतम लक्ष्य डिवाइस (जिसे मैं परीक्षण के लिए उपयोग करता हूं) के लिए उचित लगता है, जो एक Google नेक्सस 7 2013 है जिसमें एक अच्छा क्वाड कोर सीपीयू और 2 जीबी मेमोरी है।एंड्रॉइड बनावट दृश्य/चित्रकारी/चित्रकारी प्रदर्शन

मेरी आवश्यकताओं में से एक यह है कि मेरा विचार एक दृश्य के अंदर होना चाहिए जो इसे ज़ूम इन और आउट और पैन करने की अनुमति देता है, जो मैंने लिखा है कि सभी कस्टम कोड (आईओएस से UIScrollView सोचें)।

मैंने ऑनड्रा के साथ एक नियमित दृश्य (TextureView नहीं) का उपयोग करने का प्रयास किया है और प्रदर्शन बिल्कुल भयानक था - 1 फ्रेम से कम एक सेकंड। यह तब भी हुआ जब मैंने Invalidate(rect) को केवल उस आयत के साथ बुलाया। मैंने दृश्य के लिए हार्डवेयर त्वरण को बंद करने का प्रयास किया, लेकिन कुछ भी प्रस्तुत नहीं किया गया, मुझे लगता है कि सॉफ़्टवेयर के लिए 4096x4096 बहुत बड़ा है।

मैंने फिर TextureView का उपयोग करने की कोशिश की और प्रदर्शन थोड़ा बेहतर है - प्रति सेकंड लगभग 5-10 फ्रेम (अभी भी भयानक लेकिन बेहतर)। उपयोगकर्ता बिटमैप में खींचता है, जिसे बाद में पृष्ठभूमि थ्रेड का उपयोग करके बनावट में खींचा जाता है। मैं ज़ैमरिन का उपयोग कर रहा हूं लेकिन उम्मीद है कि कोड जावा लोगों को समझ में आता है।

private void RunUpdateThread() 
{ 
    try 
    { 
     TimeSpan sleep = TimeSpan.FromSeconds(1.0f/60.0f); 
     while (true) 
     { 
      lock (dirtyRect) 
      { 
       if (dirtyRect.Width() > 0 && dirtyRect.Height() > 0) 
       { 
        Canvas c = LockCanvas(dirtyRect); 
        if (c != null) 
        { 
         c.DrawBitmap(bitmap, dirtyRect, dirtyRect, bufferPaint); 
         dirtyRect.Set(0, 0, 0, 0); 
         UnlockCanvasAndPost(c); 
        } 
       } 
      } 
      Thread.Sleep(sleep); 
     } 
    } 
    catch 
    { 
    } 
} 

अगर मैं lockCanvas बदलने एक रेक्ट के बजाय अशक्त पारित करने के लिए, प्रदर्शन 60 fps महान है, लेकिन TextureView झिलमिलाहट की सामग्री और दूषित है, जो निराशाजनक है। मैंने सोचा होगा कि यह केवल ओपनजीएल फ्रेम बफर/नीचे रेंडर बनावट का उपयोग करेगा या कम से कम सामग्री को संरक्षित करने का विकल्प होगा।

क्या ड्रॉ कॉल के बीच संरक्षित सतह पर उच्च प्रदर्शन ड्राइंग और चित्रकला के लिए एंड्रॉइड में कच्चे ओपनजीएल में सबकुछ करने से कोई अन्य विकल्प कम है?

+0

4096x4096 बहुत बड़ा है। मुझे आश्चर्य नहीं है कि आपके पास प्रदर्शन समस्याएं हैं। क्या आपको बिल्कुल एक ऐसी छवि चाहिए जो बड़ी है? आप ग्राफिक्स आर्किटेक्चर देख सकते हैं: https://source.android.com/devices/graphics/architecture.html। –

+0

@ डेविड एम हाँ यह एक आवश्यकता है। मैंने कोर ग्राफिक्स, यूआईएसक्रोल व्यू और आईपैड एयर पर आईओएस पर एक समान ऐप बनाया है, प्रदर्शन 4096x4096 पर ठीक है। 2013 नेक्सस 7 को आईपैड एयर से बेहतर प्रदर्शन करना चाहिए या कम से कम तुलनीय मुझे लगता है। – jjxtra

+0

@ डेविड एम मुझे वास्तव में क्या मिलता है यह है कि यदि मैं लॉक कैनवास कॉल को शून्य करता हूं तो प्रदर्शन बहुत अच्छा होता है, लेकिन फिर बनावट की सामग्री दूषित हो जाती है ... यह बहुत अजीब बात है कि लॉक कैनवास (रेक्ट) लॉक से धीमा होगा (नल) ... – jjxtra

उत्तर

1

अद्यतन मैंने TextureView को हटा दिया और अब एक ओपनजीएल व्यू का उपयोग करें जहां मैं एक रेंडर बनावट के बदले टुकड़ों को अपडेट करने के लिए glTexSubImage2D कहता हूं।

पुराना उत्तर मैंने 4x4 ग्रिड में TextureView को टाइलिंग समाप्त कर दिया। प्रत्येक फ्रेम के गंदे आय के आधार पर, मैं उचित TextureView दृश्यों को रीफ्रेश करता हूं। कोई भी दृश्य जो उस फ्रेम को अपडेट नहीं किया गया है जिसे मैं अमान्य करता हूं।

कुछ डिवाइस, जैसे मोटो जी फोन में एक समस्या है जहां एक फ्रेम के लिए डबल बफरिंग दूषित हो जाती है। जब आप मूल दृश्य को चालू करते हैं तो लॉक कैनवास को दो बार कॉल करके आप इसे ठीक कर सकते हैं।

private void InvalidateRect(int l, int t, int r, int b) 
{ 
    dirtyRect.Set(l, t, r, b); 

    foreach (DrawSubView v in drawViews) 
    { 
     if (Rect.Intersects(dirtyRect, v.Clip)) 
     { 
      v.RedrawAsync(); 
     } 
     else 
     { 
      v.Invalidate(); 
     } 
    } 

    Invalidate(); 
} 

protected override void OnLayout(bool changed, int l, int t, int r, int b) 
{ 
    for (int i = 0; i < ChildCount; i++) 
    { 
     View v = GetChildAt(i); 
     v.Layout(v.Left, v.Top, v.Right, v.Bottom); 
     DrawSubView sv = v as DrawSubView; 
     if (sv != null) 
     { 
      sv.RedrawAsync(); 

      // why are we re-drawing you ask? because of double buffering bugs in Android :) 
      PostDelayed(() => sv.RedrawAsync(), 50); 
     } 
    } 
} 
+0

एफडब्ल्यूआईडब्ल्यू, http://stackoverflow.com/questions/31124496/ की एक ही समस्या थी, और अवैध को कॉल करके इसे हल करने में भी सक्षम था। मैंने http://b.android.com/178525 दायर किया है। – fadden

+0

बग 'एल' में तय किया गया था। विवरण के लिए http://b.android.com/178525 देखें। – fadden

+0

@fadden ऐसा लगता है, यह बढ़िया है! मैंने अपने ग्रिड में लेयरिंग सॉफ़्टवेयर दृश्यों को समाप्त कर दिया क्योंकि उन्होंने TextureView और हार्डवेयर परतों दोनों से बेहतर प्रदर्शन किया था। – jjxtra

14

सबसे पहले, अगर आप को समझने के लिए क्या हुड के नीचे हो रहा है चाहते हैं, आप Android Graphics Architecture document पढ़ने की जरूरत है। यह लंबा है, लेकिन यदि आप ईमानदारी से "क्यों" समझना चाहते हैं, तो यह शुरू करने का स्थान है।

TextureView

TextureView बारे में इस तरह से काम करता है: यह एक सतह है, जो एक निर्माता-उपभोक्त रिश्ते के साथ बफ़र्स की एक कतार है। यदि आप सॉफ़्टवेयर (कैनवास) प्रतिपादन का उपयोग कर रहे हैं, तो आप सतह को लॉक करते हैं, जो आपको बफर देता है; आप इसे आकर्षित करते हैं; फिर आप सतह को अनलॉक करते हैं, जो उपभोक्ता को बफर भेजता है। इस मामले में उपभोक्ता एक ही प्रक्रिया में है, और इसे सतह के मिश्रण या (आंतरिक रूप से, अधिक उपयुक्त) जीएलकंस्यूमर कहा जाता है। यह बफर को ओपनजीएल ईएस बनावट में परिवर्तित करता है, जिसे फिर दृश्य में प्रस्तुत किया जाता है।

यदि आप हार्डवेयर त्वरण को बंद करते हैं, तो GLES अक्षम है, और TextureView कुछ भी नहीं कर सकता है। यही कारण है कि जब आप हार्डवेयर त्वरण बंद कर दिया तो आपको कुछ भी नहीं मिला। The documentation बहुत विशिष्ट है: "TextureView का उपयोग केवल हार्डवेयर त्वरित विंडो में किया जा सकता है। सॉफ़्टवेयर में प्रस्तुत किए जाने पर, TextureView कुछ भी नहीं खींचा जाएगा।"

यदि आप एक गंदे आय निर्दिष्ट करते हैं, तो सॉफ्टवेयर रेंडरर में पिछली सामग्री को प्रतिपादन के बाद पूरा कर देगा। मुझे विश्वास नहीं है कि यह एक क्लिप रेक्ट सेट करता है, इसलिए यदि आप DrawColor() को कॉल करते हैं, तो आप पूरी स्क्रीन भर देंगे, और उसके बाद उन पिक्सल को ओवरराइट किया जाएगा। यदि आप वर्तमान में क्लिप रेक्ट सेट नहीं कर रहे हैं, तो आप ऐसा करने से कुछ प्रदर्शन लाभ देख सकते हैं। (हालांकि मैंने कोड को चेक नहीं किया था।)

गंदे रेक्ट एक इन-आउट पैरामीटर है। जब आप lockCanvas() पर कॉल करते हैं, तो आप जिस रेक्ट को चाहते हैं उसे पास करते हैं, और सिस्टम को कॉल रिटर्न से पहले इसे संशोधित करने की अनुमति है। (व्यावहारिक रूप से, ऐसा करने का एकमात्र कारण यह होगा कि अगर कोई पिछला फ्रेम नहीं था या सतह का आकार बदल दिया गया था, तो इस मामले में यह पूरी स्क्रीन को कवर करने के लिए इसका विस्तार करेगा। मुझे लगता है कि यह अधिक प्रत्यक्ष रूप से संभाला जाएगा "मैंने आपके रेक्ट को अस्वीकार कर दिया" सिग्नल।) आपको वापस आने वाले रेक्ट के अंदर प्रत्येक पिक्सेल को अपडेट करना होगा। आप को रेक्ट को बदलने की अनुमति नहीं देते हैं, जो आप अपने नमूने में करने की कोशिश कर रहे हैं - जो भी lockCanvas() सफल होने के बाद गंदे आय में है, वह है जिसे आपको आकर्षित करने की आवश्यकता है।

मुझे संदेह है कि गंदा रेक्ट गलत प्रबंधन आपके झटके का स्रोत है। अफसोस की बात है, यह बनाने के लिए एक आसान गलती है, क्योंकि lockCanvas()dirtyRect तर्क का व्यवहार केवल the Surface class में ही दस्तावेज किया गया है।

सतह और बफरिंग

सभी सतहों डबल या ट्रिपल बफ़र कर रहे हैं। इसके चारों ओर कोई रास्ता नहीं है - आप एक साथ पढ़ और लिख नहीं सकते और फाड़ नहीं सकते। यदि आप एक बफर चाहते हैं जिसे आप वांछित करते समय संशोधित और धक्का दे सकते हैं, तो उस बफर को लॉक, कॉपी और अनलॉक करने की आवश्यकता होगी, जो संरचना पाइपलाइन में स्टालों बनाता है। सर्वश्रेष्ठ थ्रूपुट और विलंबता के लिए, बफर फ़्लिप बेहतर है।

यदि आप लॉक-कॉपी-अनलॉक व्यवहार चाहते हैं, तो आप इसे स्वयं लिख सकते हैं (या एक पुस्तकालय जो इसे करता है), और यह उतना ही कुशल होगा जितना कि सिस्टम ने आपके लिए किया था (आपको लगता है ब्लिट लूप के साथ अच्छा है)। ऑफ-स्क्रीन कैनवास पर जाएं और बिटमैप को ब्लूट करें, या ओपनजीएल ईएस एफबीओ पर जाएं और बफर को ब्लिट करें। आप ग्राफिका के "record GL app" गतिविधि में उत्तरार्द्ध का एक उदाहरण पा सकते हैं, जिसमें एक मोड है जो एक बार ऑफ-स्क्रीन प्रस्तुत करता है, और फिर दो बार ब्लिट्ज करता है (एक बार डिस्प्ले के लिए, वीडियो रिकॉर्ड करने के लिए एक बार)।

अधिक गति और इस तरह के

वहाँ Android पर पिक्सल आकर्षित करने के लिए दो बुनियादी तरीके हैं: कैनवास के साथ, या ओपन के साथ। एक सतह या बिटमैप को कैनवास प्रतिपादन हमेशा सॉफ्टवेयर में किया जाता है, जबकि ओपीजीएल प्रतिपादन जीपीयू के साथ किया जाता है। एकमात्र अपवाद यह है कि, custom View पर प्रतिपादन करते समय, आप हार्डवेयर त्वरण का उपयोग करने का विकल्प चुन सकते हैं, लेकिन यह SurfaceView या TextureView की सतह पर प्रतिपादन करते समय लागू नहीं होता है।

एक ड्राइंग या पेंटिंग ऐप या तो ड्राइंग कमांड को याद कर सकता है, या बस बफर पर पिक्सेल फेंक सकता है और इसकी स्मृति के रूप में उपयोग कर सकता है। पूर्व गहरा "पूर्ववत" करने की अनुमति देता है, उत्तरार्द्ध बहुत आसान है, और बढ़ने के लिए सामान की मात्रा के रूप में तेजी से बेहतर प्रदर्शन है। ऐसा लगता है जैसे आप बाद वाले करना चाहते हैं, इसलिए ऑफ-स्क्रीन से ब्लिटिंग समझ में आता है।

अधिकांश मोबाइल उपकरणों में 4096x4096 की हार्डवेयर सीमा होती है या जीएलएस बनावट के लिए छोटी होती है, इसलिए आप किसी भी चीज़ के लिए एक बनावट का उपयोग करने में सक्षम नहीं होंगे। आप आकार सीमा मान (GL_MAX_TEXTURE_SIZE) से पूछ सकते हैं, लेकिन आप एक आंतरिक बफर के साथ बेहतर हो सकते हैं जो आप जितना चाहें उतना बड़ा हो, और केवल उस भाग को प्रस्तुत करें जो स्क्रीन पर फिट बैठता है। मुझे नहीं पता कि स्कीया (कैनवास) सीमा क्या है, लेकिन मेरा मानना ​​है कि आप बहुत अधिक बिटमैप्स बना सकते हैं।

आपकी ज़रूरतों के आधार पर, एक SurfaceView एक TextureView के लिए बेहतर हो सकता है, क्योंकि यह मध्यवर्ती जीएलएस बनावट चरण से बचाता है। सतह पर जो कुछ भी आप आकर्षित करते हैं वह सीधे सिस्टम कंपोजिटर (सतहफ्लिंगर) पर जाता है। इस दृष्टिकोण के लिए नीचे की तरफ यह है कि, क्योंकि सतह का उपभोक्ता प्रक्रिया में नहीं है, इसलिए दृश्य प्रणाली को आउटपुट को संभालने का कोई मौका नहीं है, इसलिए सतह एक स्वतंत्र परत है। (एक ड्राइंग प्रोग्राम के लिए यह फायदेमंद हो सकता है - खींची जा रही छवि एक परत पर है, आपका यूआई शीर्ष पर एक अलग परत पर है।)

एफडब्ल्यूआईडब्ल्यू, मैंने कोड को नहीं देखा है, लेकिन डैन सैंडलर के Markers ऐप एक चोटी के लायक हो सकता है (स्रोत कोड here)।

अद्यतन: भ्रष्टाचार identified as a bug और fixed in 'L' था।

+0

जब मैंने हार्डवेयर त्वरण बंद कर दिया था तो मैं ओवरड्रा के साथ एक मानक दृश्य का उपयोग कर रहा था। क्षमा करें अगर वह स्पष्ट नहीं था। – jjxtra

+0

आपकी अच्छी तरह से सोचा प्रतिक्रिया के लिए धन्यवाद। मैंने टाइलिंग व्यू व्यू व्यू के साथ प्रयोग किया है, लेकिन मुझे कुछ अजीब प्रतिपादन कलाकृतियां मिल रही हैं जब मैं त्वरित उत्तराधिकार में एकाधिक बनावट दृश्यों के कैनवास को लॉक और पोस्ट करता हूं। कोई विचार क्यों हो सकता है? क्या TextureView केवल एक उदाहरण का समर्थन करता है? – jjxtra

+1

SurfaceView एक अच्छा विकल्प नहीं है क्योंकि मैं अपने स्वयं के कस्टम व्यू ग्रुप के अंदर ड्रॉ व्यू को एम्बेड कर रहा हूं जो ज़ूमिंग और पैनिंग के लिए UIScrollView के रूप में कार्य करता है। – jjxtra

संबंधित मुद्दे