معدن أبطأ بكثير مقارنة ببرنامج OpenGL أثناء عرض مواد صغيرة على نسيج كبير

0

أحاول ترحيل مشروعاتي من OpenGL إلى Metal على iOS. ولكن يبدو أنني أصاب جدار الأداء. المهمة بسيطة ...

لدي نسيج كبير (أكثر من 3000x3000 بكسل). أحتاج إلى رسم عدة مواد صغيرة (بضع مئات) (مثل 124 × 124) في كل حدث تم لمسه. وهذا أثناء تمكين وظيفة مزج معينة. إنها في الأساس مثل فرشاة الطلاء. ثم اعرض الملمس الكبير. هذه هي المهمة تقريبًا.

يعمل على OpenGL بسرعة كبيرة. أحصل على 60 إطارًا في الثانية تقريبًا. عندما أقوم بنقل نفس الرمز إلى Metal ، يمكنني أن أحصل على 15 إطارًا في الثانية فقط.

لقد قمت بإنشاء مشروعين نموذجيين بحد أدنى عارية لإثبات المشكلة. هنا المشاريع (OpenGL و Metal) ...

https://drive.google.com/file/d/12MPt1nMzE2UL_s4oXEUoTCXYiTz42r4b/view؟usp=sharing

هذا تقريبًا ما أقوم به في برنامج OpenGL ...

    - (void) renderBrush:(GLuint)brush on:(GLuint)fbo ofSize:(CGSize)size at:(CGPoint)point {
    GLfloat brushCoordinates[] = {
        0.0f, 0.0f,
        1.0f, 0.0f,
        0.0f,  1.0f,
        1.0f,  1.0f,
    };

    GLfloat imageVertices[] = {
        -1.0f, -1.0f,
        1.0f, -1.0f,
        -1.0f,  1.0f,
        1.0f,  1.0f,
    };

    int brushSize = 124;

    CGRect rect = CGRectMake(point.x - brushSize/2, point.y - brushSize/2, brushSize, brushSize);

    rect.origin.x /= size.width;
    rect.origin.y /= size.height;
    rect.size.width /= size.width;
    rect.size.height /= size.height;

    [self convertImageVertices:imageVertices toProjectionRect:rect onImageOfSize:size];

    int currentFBO;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &currentFBO);

    [_Program use];

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glViewport(0, 0, (int)size.width, (int)size.height);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, brush);
    glUniform1i(brushTextureLocation, 2);

    glVertexAttribPointer(positionLocation, 2, GL_FLOAT, 0, 0, imageVertices);
    glVertexAttribPointer(brushCoordinateLocation, 2, GL_FLOAT, 0, 0, brushCoordinates);

    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisable(GL_BLEND);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, 0);

    glBindFramebuffer(GL_FRAMEBUFFER, currentFBO);
}

أقوم بتشغيل هذا الرمز في حلقة (حوالي 200-500) لكل حدث لمسة. يعمل بسرعة كبيرة.

وبهذه الطريقة قمت بنقل الرمز إلى Metal ...

- (void) renderBrush:(id<MTLTexture>)brush onTarget:(id<MTLTexture>)target at:(CGPoint)point withCommandBuffer:(id<MTLCommandBuffer>)commandBuffer {

int brushSize = 124;

CGRect rect = CGRectMake(point.x - brushSize/2, point.y - brushSize/2, brushSize, brushSize);

rect.origin.x /= target.width;
rect.origin.y /= target.height;
rect.size.width /= target.width;
rect.size.height /= target.height;

Float32 imageVertices[8];
// Calculate the vertices (basically the rectangle that we need to draw) on the target texture that we are going to draw
// We are not drawing on the entire target texture, only on a square around the point
[self composeImageVertices:imageVertices toProjectionRect:rect onImageOfSize:CGSizeMake(target.width, target.height)];

// We use different one vertexBuffer per pass. This is because this is run on a loop and the subsequent calls will overwrite
// The values. Other buffers also get overwritten but that is ok for now, we only need to demonstrate the performance.
id<MTLBuffer> vertexBuffer = [_vertexArray lastObject];

memcpy([vertexBuffer contents], imageVertices, 8 * sizeof(Float32));

id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:mRenderPassDescriptor];
commandEncoder.label = @"DrawCE";

[commandEncoder setRenderPipelineState:mPipelineState];

[commandEncoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[commandEncoder setVertexBuffer:mBrushTextureBuffer offset:0 atIndex:1];

[commandEncoder setFragmentTexture:brush atIndex:0];
[commandEncoder setFragmentSamplerState:mSampleState atIndex:0];

[commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[commandEncoder endEncoding];

}}

ثم قم بتشغيل هذا الرمز في حلقة مع حدث MTLCommandBuffer لكل لمسة مثل ...

    id<MTLCommandBuffer> commandBuffer = [MetalContext.defaultContext.commandQueue commandBuffer];
commandBuffer.label = @"DrawCB";

dispatch_semaphore_wait(_inFlightSemaphore, DISPATCH_TIME_FOREVER);

mRenderPassDescriptor.colorAttachments[0].texture = target;

__block dispatch_semaphore_t block_sema = _inFlightSemaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
    dispatch_semaphore_signal(block_sema);
}];

_vertexArray = [[NSMutableArray alloc] init];
for (int i = 0; i < strokes; i++) {
    id<MTLBuffer> vertexBuffer = [MetalContext.defaultContext.device newBufferWithLength:8 * sizeof(Float32) options:0];
    [_vertexArray addObject:vertexBuffer];

    id<MTLTexture> brush = [_brushes objectAtIndex:rand()%_brushes.count];
    [self renderBrush:brush onTarget:target at:CGPointMake(x, y) withCommandBuffer:commandBuffer];
    x += deltaX;
    y += deltaY;
}

[commandBuffer commit];

في نموذج الرمز الذي أرفقته ، قمت باستبدال أحداث اللمس بحلقة توقيت لإبقاء الأمور بسيطة.

على iPhone 7 Plus ، أحصل على 60 إطارًا في الثانية مع OpenGL و 15 إطارًا في الثانية مع Metal. ربما أفعل شيئا خاطئا بشكل فظيع هنا؟

1 إجابة

3
افضل جواب

إزالة كل التكرار:

  • لا تقم بإنشاء مخازن مؤقتة في وقت التقديم. تخصيص مخازن كافية أثناء التهيئة.
  • لا تنشئ ترميز الأوامر لكل رباعية.
  • استخدم مخزنًا مؤقتًا كبيرًا واحدًا للرأس مع إزاحة مختلفة (محاذاة بشكل صحيح) لكل رباعية. استعمال -setVertexBufferOffset:atIndex: لتعيين الإزاحة فقط حسب الضرورة ، دون تغيير المخزن المؤقت.
  • composeImageVertices:... يمكن أن تكتب مباشرة في المنطقة العازلة مع المدلى بها المناسب ، وتجنب أ memcpy .
  • اعتمادا على ما composeImageVertices:... فعلا وإذا deltaX و deltaY هي ثوابت ، قد تكون قادرًا على إعداد المخزن المؤقت قمة الرأس مرة واحدة ، على الإطلاق. يستطيع جهاز تظليل الرأس تحويل القمم حسب الضرورة. يمكنك إدخال البيانات المناسبة كزي موحد (إما نقطة الوجهة وتجسيد حجم الهدف أو حتى مصفوفة التحويل).
  • بافتراض أنهما متماثلان في كل مرة ، لا تضبط mPipelineState ، mBrushTextureBuffer و mSampleState كل مره.
  • إذا كانت هناك أي كواد تشترك في نفس نسيج الفرشاة ، فقم بتجميعها معًا وقم بتنفيذ أمر رسم واحد لرسمها جميعًا. قد يتطلب هذا التبديل إلى بدائل المثلث بدلاً من بدائية مثلثية الشكل. ومع ذلك ، إذا قمت برسم مفهرس ، يمكنك استخدام الحارس البدائي لإعادة التشغيل لرسم شرائط مثلث متعددة في أمر سحب واحد.
  • يمكنك حتى إجراء فرش متعددة في أمر رسم واحد إذا لم يتجاوز العدد عدد القوام المسموح به (31). مرر كل نسيج الفرشاة إلى تظليل الجزء. يمكن استقبالها كمصفوفة نسيج. ستشمل بيانات قمة الرأس مؤشر الفرشاة ، وسوف يمرر تظليل الرأس ذلك إلى الأمام ، وسيستخدمه تظليل الجزء للبحث عن النسيج لأخذ عينة من الصفيف.
  • يمكنك استخدام الرسم غير المتوازن لرسم كل شيء في أمر واحد. رسم stroke حالات رباعية واحدة. في تظليل الرأس ، قم بتحويل الموضع بناءً على معرف المثيل. يجب أن تمر deltaX و deltaY في بيانات موحدة. يمكن أن تكون فهارس الفرشاة في مخزن مؤقت واحد يتم تمريره أيضًا ، ويمكن للتظليل أن يبحث عن فهرس الفرشاة فيه بواسطة معرف المثيل.
  • هل فكرت في استخدام البدائية نقطة بدلا من الكواد؟ سيقلل ذلك من عدد رؤوس الرأس ويعطي معلومات معدنية يمكن استخدامها لتحسين التنقيط.
:مؤلف

أسئلة ذات صلة

فوق
قائمة طعام