egl/egltest/src/egltest_stress_sgimage.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 18 Aug 2010 11:05:09 +0300
changeset 152 9f1c3fea0f87
parent 85 cdf2f6e5c390
permissions -rw-r--r--
Revision: 201033 Kit: 201033

// Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

/**
 @file
 @test
*/

#include <s32file.h>
#include <test/t_simload.h>
#include <test/tefunit.h>    //ASSERT_TRUE
#include "egltest_stress_sgimage.h"

TVerdict CEglTest_Stress::doTestStepL()
    {
    INFO_PRINTF1(_L("CEglTest_Stress:doTestStepL()"));
    
    TBuf<100> testCaseId;
    TestCaseName(testCaseId);
    SetTestStepID(testCaseId);

#ifdef SYMBIAN_GRAPHICS_EGL_SGIMAGELITE
    __UHEAP_MARK;
    
    //File server is used to generate simload ini file
    User::LeaveIfError(iFs.Connect());
  
    TBool ret = CheckForExtensionL(KEGL_RSgimage | KEGL_KHR_image_base | KVG_KHR_EGL_image | KEGL_KHR_image_pixmap);
    if(!ret)
        {
        // The extension is not supported
        RecordTestResultL();
        CloseTMSGraphicsStep();
        return TestStepResult();
        }

    //Create a config file and launch the simulated load app
    ReadIniValuesL();
    PrintConfigDataL();
    CreateSimLoadAppL();

    INFO_PRINTF1(_L("Creating a Semaphore to signal all child processes at once"));
    RSemaphore sem;
    User::LeaveIfError(sem.CreateGlobal(KEglStressTest(), iNumberChildProcesses));
    CleanupClosePushL(sem);

    //Information to be passed to the child process
    TSgImageInfo info;
    TSize size(iRSgImageWidth, iRSgImageHeight);
    info.iUsage = ESgUsageBitOpenVgImage | ESgUsageBitOpenVgSurface;
    info.iPixelFormat = iFormat;
    info.iSizeInPixels = size;

    //Utilise egl helper functions
    CreateEglSessionL();
    
    //Create a display and initialise it
    GetDisplayL();
    iEglSess->InitializeL();
    
    //Open RSGImage driver    
    iEglSess->OpenSgDriverL();
    
    EGL_LEAVE_ERROR(eglBindAPI(EGL_OPENVG_API));
 
    EGLContext context;
    for(TInt i=0; i<iNumberRSgImages; i++)
        {
        RSgImage image;
        iSgImageArray.InsertL(image, i);
        User::LeaveIfError(iSgImageArray[i].Create(info, NULL, NULL));

        EGLConfig config;
        if(i == 0)
            {
            //Only need to determine a matching configuration once
            ChooseConfigAndCreateContextL(iDisplay, context, config, iSgImageArray[i], KStressTestMainAppPanic, iAlphaPre);
            }
 
        EGLSurface surface = EGL_NO_SURFACE; //remove arm warning
        EGL_LEAVE_NULL(surface, CreatePixmapSurfaceL(iDisplay, config, iSgImageArray[i], iAlphaPre));

        EGL_LEAVE_ERROR(eglMakeCurrent(iDisplay, surface, surface, context));
         
        PaintSurfaceL(iDisplay, surface, context);
        EGL_LEAVE_ERROR(eglSwapBuffers(iDisplay, surface));

        if(iTestType == EStressVGImage)
            {
            EGL_LEAVE_ERROR(eglMakeCurrent(iDisplay, surface, surface, context));
            
            EGLImageKHR eglImage = 0; //removes arm compiler warning
            VGImage vgImage;

            EGL_LEAVE_NULL(eglImage, iEglSess->eglCreateImageKhrL(iDisplay, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, &iSgImageArray[i], (int*)KEglImageAttribsPreservedTrue));
            EGL_LEAVE_NULL(vgImage, iEglSess->vgCreateImageTargetKHR(eglImage));

            //Close the EGLImage
            EGL_LEAVE_ERROR(iEglSess->DestroyEGLImage(iDisplay, eglImage));
                        
            User::LeaveIfError(iVGImageArray.Insert(vgImage, i));
            }

        User::LeaveIfError(iSurfaceArray.Insert(surface, i));
        }

    /* Create and install the active scheduler */
    CActiveScheduler* sched = new(ELeave) CActiveScheduler;
    CActiveScheduler::Install(sched);
    CleanupStack::PushL(sched);

    TInt exitCounter = iNumberMainImages;
    TBool testResult = ETrue;

    //Create an active object for each RSgImage accessed in the main process
    CTReadWriteMain* painter = 0;
    for(TInt i=0; i<iNumberMainImages; i++)
        {
        if(iTestType == EStressVGImage)
            {
            painter = CTReadWriteMain::NewL(iDisplay, iSurfaceArray[i], context, iRSgImageWidth, iRSgImageHeight, iByteSize, iVgFormat, testResult, exitCounter, iTestType, iVGImageArray[i]);
            }
        else
            {
            painter = CTReadWriteMain::NewL(iDisplay, iSurfaceArray[i], context, iRSgImageWidth, iRSgImageHeight, iByteSize, iVgFormat, testResult, exitCounter, iTestType);            
            }
            
        CleanupStack::PushL(painter);
        painter->After(TTimeIntervalMicroSeconds32(0));
        }

    CreateChildProcessesL();

    INFO_PRINTF1(_L("Signaling all child processes at once - starts data access in the child processes"));
    sem.Signal(iNumberChildProcesses);

    //Start the active scheduler - starts data access in the main process
    sched->Start();

    if(testResult == EFalse)
        {
		if (iTestType == EStressReadWriteSingleImage || iTestType == EStressReadWriteMultiImage)
			{
			// For GRAPHICS-EGL-0428 and GRAPHICS-EGL-0437 data integrity cannot be guaranteed on 
			// all implementations, so the pixel value checking aspects of these tests are regarded as optional   
			WARN_PRINTF1(_L("Unexpected pixel colour"));
			}
		else
			{
			ERR_PRINTF1(_L("Unexpected pixel colour"));
			SetTestStepResult(EFail);
			}
        }

    //Check that each child process has completed without error
    for(TInt i=0; i<iNumberChildProcesses; i++)
       {
       TRequestStatus status;
       iProcessArray[i].Logon(status);
       User::WaitForRequest(status);

       if(status != KErrNone)
           {
		   if (status == KTestStressUnexpectedPixelError && (iTestType == EStressReadWriteSingleImage || iTestType == EStressReadWriteMultiImage))
			   {
			   // For GRAPHICS-EGL-0428 and GRAPHICS-EGL-0437 data integrity cannot be guaranteed on 
			   // all implementations, so the pixel value checking aspects of these tests are regarded as optional
			   // So check that if it fails, it fails for the expected reason of no matching pixel.
			   WARN_PRINTF2(_L("Child Process completed with code %d, expected KErrNone"), status.Int());
			   }
		   else
			   {
			   ERR_PRINTF2(_L("Child Process completed with code %d, expected KErrNone"), status.Int());
			   SetTestStepResult(EFail);
			   }
           }
       }

    DestroySimLoadProcess();
    DeleteConfigData();
    ClearDownArraysL(iDisplay);
    EGL_LEAVE_ERROR(eglDestroyContext(iDisplay, context));

    CleanupStack::PopAndDestroy(iNumberMainImages + 2, &sem); //(iNumberMainImages + 1) active objects, sched, sem
    iEglSess->CloseSgDriver();
    
    CleanAll();
    iFs.Close();

    __UHEAP_MARKEND;

#else
    INFO_PRINTF2(_L("%S can only be run with SgImage-Lite"), &testCaseId);
#endif //SYMBIAN_GRAPHICS_EGL_SGIMAGELITE
    
    //Comply with the terrific TMS
    RecordTestResultL();
    CloseTMSGraphicsStep();

    return TestStepResult();
    }

CEglTest_Stress::~CEglTest_Stress()
    {
    //Free memory and close handles in case of panic in the doTestStepL()
    delete iEglSess;
    
    iFs.Close();
    iSurfaceArray.Close();
    iProcessArray.Close();
    iSgImageArray.Close();
    iVGImageArray.Close();
    }

void CEglTest_Stress::PaintSurfaceL(EGLDisplay aDisplay, EGLSurface aSurface, EGLContext aContext)
    {
    //Clear surface background, format of the constant is ARGB
    //Match the colour to that of the defined constant
    VGfloat red = 0.0;
    VGfloat green = 0.0;
    VGfloat blue = 0.0;
    VGfloat alpha = 0.0;
    
    //Care taken to avoid fixed width -> floating point -> fixed width rounding errors
    if(iByteSize == 4)
        {
        red = 1.0 * ((KColourInitial32 & 0x00FF0000) >> 16)/255;
        green = 1.0 * ((KColourInitial32 & 0x0000FF00) >> 8)/255;
        blue = 1.0 * (KColourInitial32 & 0x000000FF)/255;
        alpha = 1.0 * ((KColourInitial32 & 0xFF000000) >> 24)/255;        
        }
    else //iByteSize == 2
        {
        red = 1.0 * ((KColourInitial16 & 0x7C00) >> 11)/31;
        green = 1.0 * ((KColourInitial16 & 0x0480) >> 5)/63;
        blue = 1.0 * (KColourInitial16 & 0x001F)/31;
        }

    //Format of the constant is RGBA (32 bit) 
    VGfloat bgColor[] = {red, green, blue, alpha};

    EGL_LEAVE_ERROR(eglMakeCurrent(aDisplay, aSurface, aSurface, aContext));
 
    vgSetfv(VG_CLEAR_COLOR, 4, bgColor);
    ASSERT_VG_TRUE(vgGetError() == VG_NO_ERROR);
    vgClear(0, 0, iRSgImageWidth, iRSgImageHeight);
    ASSERT_VG_TRUE(vgGetError() == VG_NO_ERROR);
    }

void CEglTest_Stress::CreateChildProcessL(TInt aProcessNumber, TTestType aTestType, TSgDrawableId aDrawableId)
    {
    TRequestStatus status;
    RProcess client;
    User::LeaveIfError(client.Create(KStressTestClientApp, KNullDesC));

    //Pass image and test information to the child process
    TStressProcessInfo info;
    info.iTestType = aTestType;
    info.iSgId = aDrawableId;
    info.iByteSize = iByteSize;
    info.iAlphaPre = iAlphaPre;

    TPckg<TStressProcessInfo> pckgInfo(info);
    User::LeaveIfError((client.SetParameter(KMultiProcessSlot, pckgInfo)));

    client.Rendezvous(status);
    client.Resume();
    User::WaitForRequest(status);
    TEST(status == KErrNone);

    //Store image handle for cleanup
    iProcessArray.InsertL(client, aProcessNumber);
    }

void CEglTest_Stress::CreateChildProcessesL()
    {
    for(TInt i=0; i<iNumberChildProcesses; i++)
        {
        switch(iTestType)
            {
            case EStressReadWriteMultiImage:
            case EStressPixmapSurface:
                {
                //Each child process accesses one SgImage
                CreateChildProcessL(i, iTestType, iSgImageArray[i].Id());
                break;
                }
            case EStressRead:
            case EStressReadWriteSingleImage:
            case EStressVGImage:
                {
                //All child processes access the same SgImage
                CreateChildProcessL(i, iTestType, iSgImageArray[0].Id());
                break;
                }
            default:
                User::Panic(KStressTestMainAppPanic, KErrNotFound);
                break;
            }
        }
    }

void CEglTest_Stress::ClearDownArraysL(EGLDisplay aDisplay)
    {
    for(TInt i=0; i<iSurfaceArray.Count(); i++)
        {
        ASSERT_EGL_TRUE(eglDestroySurface(aDisplay, iSurfaceArray[i]));
        }
    
    for(TInt i=0; i<iVGImageArray.Count(); i++)
        {
        vgDestroyImage(iVGImageArray[i]);
        ASSERT_VG_TRUE(vgGetError() == VG_NO_ERROR);
        }
    
    for(TInt i=0; i<iProcessArray.Count(); i++)
        {
        iProcessArray[i].Close();
        }

    for(TInt i=0; i<iSgImageArray.Count(); i++)
        {
        iSgImageArray[i].Close();
        }
    
    iSurfaceArray.Close();
    iProcessArray.Close();
    iSgImageArray.Close();
    iVGImageArray.Close();
    }

void CEglTest_Stress::CreateSimLoadAppL()
    {
    TInt index = 1;
    TBuf<100> tempStore;

    //Three simload processes need to be launched t_simloadapp1.exe - t_simloadapp3.exe
    while(index <= KNumSimLoadApps)
        {
        tempStore.Format(KSimLoadApp, index++);
        CreateSimLoadProcessL(tempStore);
        }
    }

void CEglTest_Stress::CreateSimLoadProcessL(const TDesC& aApp)
    {
    INFO_PRINTF2(_L("Starting App: %S"), &aApp);

    RProcess process;
    User::LeaveIfError(process.Create(aApp, KNullDesC));

    //Give the simulated load high priority to be sure it does its job
    process.SetPriority(EPriorityHigh);
    TEST(process.Priority() == EPriorityHigh);
    INFO_PRINTF3(_L("Process Priority: Actual: %d, Expected: %d"), process.Priority(), EPriorityHigh);
    process.Resume();
    
    iProcessList.AppendL(process);
    }

void CEglTest_Stress::DestroySimLoadProcess()
    {
    for (TInt index = 0; index < iProcessList.Count(); index++)
        {
        // check process
        INFO_PRINTF3(_L("Process Check: Actual: %d, Expected: %d"), iProcessList[index].ExitReason(), KErrNone);
        TEST(iProcessList[index].ExitReason( )== KErrNone);

        // kill process
        iProcessList[index].Kill(KErrGeneral);
        INFO_PRINTF3(_L("Process Exit Reason: Actual: %d, Expected: %d"), iProcessList[index].ExitReason(), KErrGeneral);
        TEST(iProcessList[index].ExitReason() == KErrGeneral);

        iProcessList[index].Close();
        }

    iProcessList.Close();
    }

void CEglTest_Stress::ReadIniValueL(const TDesC& aSectName, const TDesC& aKeyName, TInt& aResult)
    {
    if(!GetIntFromConfig(aSectName, aKeyName, aResult))
        {
        ERR_PRINTF2(_L("Error reading %S value from ini file"), &aKeyName);
        User::Leave(KErrNotFound);
        }

    INFO_PRINTF3(_L("Ini file value %S = %d"), &aKeyName, aResult);
    }

void CEglTest_Stress::ReadIniValuesL()
    {
    ReadIniValueL(ConfigSection(), KNumberRSgImages, iNumberRSgImages);
    ReadIniValueL(ConfigSection(), KNumberMainImages, iNumberMainImages);
    ReadIniValueL(ConfigSection(), KNumberChildProcesses, iNumberChildProcesses);
    ReadIniValueL(ConfigSection(), KRSgImageWidth, iRSgImageWidth);
    ReadIniValueL(ConfigSection(), KRSgImageHeight, iRSgImageHeight);
    ReadIniValueL(ConfigSection(), KConfigSimLoadValue, iSimLoadValue);
    
    //Check for erroneous ini values
    ASSERT_TRUE(iNumberChildProcesses <= iNumberRSgImages);
    ASSERT_TRUE(iNumberMainImages <= iNumberRSgImages);
    ASSERT_TRUE(iRSgImageWidth >= 0);
    ASSERT_TRUE(iRSgImageHeight >= 0);
    ASSERT_TRUE(iSimLoadValue >= 0);
    ASSERT_TRUE(iSimLoadValue <= 100);
    
    TBuf16<100> buffer;
    TPtrC16 ptrBuffer(buffer);
    if(!GetStringFromConfig(ConfigSection(), KPixelFormat, ptrBuffer))
        {
        ERR_PRINTF2(_L("Error reading %S value from ini file"), &KPixelFormat);
        User::Leave(KErrNotFound);
        }

    INFO_PRINTF3(_L("Ini file value %S = %S"), &KPixelFormat, &ptrBuffer);

    //Derive information from the pixel format
    if(ptrBuffer == KUidPixelFormatARGB_8888)
        {
        iFormat = EUidPixelFormatARGB_8888;
        iVgFormat = VG_sARGB_8888;
        iAlphaPre = EFalse;
        iByteSize = 4;
        }
    else if(ptrBuffer == KUidPixelFormatARGB_8888_PRE)
        {
        iFormat = EUidPixelFormatARGB_8888_PRE;
        iVgFormat = VG_sARGB_8888_PRE;
        iAlphaPre = ETrue;
        iByteSize = 4;
        }
    else if(ptrBuffer == KUidPixelFormatRGB_565)
        {
        iFormat = EUidPixelFormatRGB_565;
        iVgFormat = VG_sRGB_565;
        iAlphaPre = EFalse;
        iByteSize = 2;
        }
    else
        {
        ERR_PRINTF2(_L("Unsupported pixel format %S"), &ptrBuffer);
        User::Leave(KErrNotFound);
        }

     //Determine the test type from the ini file section name
     if(ConfigSection().Find(KStressReadOnly) != KErrNotFound)
         {
         iTestType = EStressRead;
         }
     else if(ConfigSection().Find(KEStressReadWriteSingleImage) != KErrNotFound)
         {
         iTestType = EStressReadWriteSingleImage;
         }
     else if(ConfigSection().Find(KStressReadWriteMultiImage) != KErrNotFound)
         {
         iTestType = EStressReadWriteMultiImage;
         }
     else if(ConfigSection().Find(KStressVGImage) != KErrNotFound)
         {
         iTestType = EStressVGImage;
         }
     else if(ConfigSection().Find(KStressPixmapSurface) != KErrNotFound)
         {
         iTestType = EStressPixmapSurface;
         }
     else
         {
         ERR_PRINTF2(_L("Unknown test case %S"), &ptrBuffer);
         User::Leave(KErrNotFound);
         }
    }

/** 
 *   This function generates an ini file for the simulated load application
 *   containing two lines to determine
 *   1. The type of load (static or spiked, in this case always static)
 *   2. The simulated load level
 */
void CEglTest_Stress::PrintConfigDataL()
    {
    RFileWriteStream writer;
    writer.PushL();

    TInt err = iFs.MkDirAll(KSimLoadConfigFile);
    TEST(err == KErrNone || err == KErrAlreadyExists);
    INFO_PRINTF2(_L("Create Config File: %S"), &KSimLoadConfigFile);
    User::LeaveIfError(writer.Replace(iFs, KSimLoadConfigFile, EFileStreamText|EFileWrite));
    writer.CommitL();

    CleanupStack::PopAndDestroy(&writer);

    CIniData* data=CIniData::NewL(KSimLoadConfigFile);
    CleanupStack::PushL(data);

    INFO_PRINTF3(_L("Config Name: %S, \t\tConfig Data: %S"), &KConfigSimLoadType, &KConfigSimLoadStatic);
    err = data->AddValue(KDefaultSectionName, KConfigSimLoadType, KConfigSimLoadStatic);
    INFO_PRINTF3(_L("AddValue - Expected: %d, Actual: %d"), KErrNone, err);
    TEST(err == KErrNone);

    TBuf16<100> buffer;
    TPtrC16 ptrBuffer(buffer);
    User::LeaveIfError(GetStringFromConfig(ConfigSection(), KConfigSimLoadValue, ptrBuffer));

    INFO_PRINTF3(_L("Config Name: %S, \t\tConfig Data: %S"), &KConfigSimLoadValue, &ptrBuffer);
    err = data->AddValue(KDefaultSectionName, KConfigSimLoadValue, ptrBuffer);

    INFO_PRINTF3(_L("AddValue - Expected: %d, Actual: %d"), KErrNone, err);
    TEST(err == KErrNone);

    data->WriteToFileL();
    CleanupStack::PopAndDestroy(data);
    }

void CEglTest_Stress::DeleteConfigData()
    {
    INFO_PRINTF2(_L("Deleting Config File Name: %S"), &KSimLoadConfigFile);
    TInt err = iFs.Delete(KSimLoadConfigFile);
    TEST(err==KErrNone);
    }

/**
 *    class CTReadWriteMain
 *    A Child of CTReadWrite which contains member data not included in the base class and 
 *    implementations of pure virtual functions.
 *    a) One for each particular test case
 *    b) Support functions MakeCurrentL() and IsFinished()
 *    
 *    The base class is an active object and the implemented virtual functions are invoked
 *    indirectly from the RunL() function
 */
CTReadWriteMain::CTReadWriteMain(EGLDisplay aDisplay, EGLSurface aSurface, EGLContext aContext, TInt aWidth, TInt aHeight, TInt aByteSize, VGImageFormat aFormat, TBool& aTestPass, TInt& aFinishedCounter, const TTestType& aTestType)
: CTReadWrite(aWidth, aHeight, aByteSize, aFormat, aTestType, aTestPass),
    iDisplay(aDisplay),
    iSurface(aSurface),
    iContext(aContext),
    iFinishedCounter(aFinishedCounter)
    {
    }

CTReadWriteMain* CTReadWriteMain::NewL(EGLDisplay aDisplay, EGLSurface aSurface, EGLContext aContext, TInt aWidth, TInt aHeight, TInt aByteSize, VGImageFormat aFormat, TBool& aTestPass, TInt& aFinishedCounter, const TTestType& aTestType, VGImage aVGImage)
    {
    CTReadWriteMain* self = new (ELeave) CTReadWriteMain(aDisplay, aSurface, aContext, aWidth, aHeight, aByteSize, aFormat, aTestPass, aFinishedCounter, aTestType);
    CleanupStack::PushL(self);
    self->ConstructL(aVGImage);
    CleanupStack::Pop(self);
    return self;
    }

void CTReadWriteMain::ConstructL(VGImage aVGImage)
    {
    //NULL values indicate a programming error
    if( (iDisplay == EGL_NO_DISPLAY) || (iSurface == EGL_NO_SURFACE) || (iContext == EGL_NO_CONTEXT) || (iFinishedCounter == 0) || ((iTestType == EStressVGImage) && (aVGImage == NULL)) )
        {
        User::Leave(KErrArgument);
        }
    
    iVGImage = aVGImage;
    
    //Call base class function to complete construction
     CTReadWrite::ConstructL();
    }

void CTReadWriteMain::MakeCurrentL() const
    {
    EGL_LEAVE_ERROR(eglMakeCurrent(iDisplay, iSurface, iSurface, iContext));
    }

void CTReadWriteMain::ReadImageFuncL()
    {
    vgReadPixels(iData, iWidth*iByteSize, iFormat, 0, 0, iWidth, iHeight);
    VgLeaveIfErrorL();
    }

void CTReadWriteMain::ReadFuncL()
    {
    ReadImageFuncL();
    }

void CTReadWriteMain::WriteImageFuncL()
    {
    vgWritePixels(iData, iWidth*iByteSize, iFormat, 0, 0, iWidth, iHeight);
    VgLeaveIfErrorL();
    }

TBool CTReadWriteMain::IsFinished()
    {
    iFinishedCounter--;

    if(iFinishedCounter <= 0)
        {
        return ETrue;
        }

    return EFalse;
    }

void CTReadWriteMain::VgImageFuncL()
    {
    //Alter the image data, actual pixel values are not important
    for(TInt i=0; i<iBufferSize; i++)
        {
        const TUint32 temp = iBufferSize % iFrameNumber;
        iData[i] = temp + (temp << 8) + (temp << 16) + (temp << 24);
        }
    
    //Currently panics as context->scanlinebuffer is NULL
    vgImageSubData(iVGImage, iData, iWidth*iByteSize, iFormat, 0, 0, iWidth, iHeight);
    VgLeaveIfErrorL();
    
    EGL_LEAVE_ERROR(eglSwapBuffers(iDisplay, iSurface));
    }

void CTReadWriteMain::PixmapSurfaceFuncL()
    {
    // clear surface background to an arbitrary colour
    VGfloat arbitraryColour = (1.0*iFrameNumber)/KNumberOfFrames;
    VGfloat backgroundColour[] = {1.0 - arbitraryColour/2, arbitraryColour/2, 1.0 - arbitraryColour, arbitraryColour};
    vgSetfv(VG_CLEAR_COLOR, 4, backgroundColour);
    VgLeaveIfErrorL();
    vgClear(0, 0, iWidth, iHeight);
    VgLeaveIfErrorL();

    EGL_LEAVE_ERROR(eglSwapBuffers(iDisplay, iSurface));
    }