지직전기

[최적화] OpenGL 렌더링 성능향상 - Display List 본문

STUDY/Troubleshooting

[최적화] OpenGL 렌더링 성능향상 - Display List

MSH103 2025. 4. 20. 23:10

성능 최적화 이전(좌), 이후(우)


문제 발생

프로그램을 다중 실행하거나, 지도에 표시되는 오브젝트(Shapefile 등)의 개수가 많아질수록
지도 이동이나 확대/축소 시 전시 화면이 뚝뚝 끊기는 현상이 발생.

 - 기존 3ms 내외였던 전시 주기가 최대 200~300ms까지 지연

 - 마우스 반응도 느려지고, 전체적인 사용자 경험(UX) 저하

 

원인 분석

OpenGL을 이용해 맵에 지형 및 오브젝트 등 항목데이터를 실시간으로 렌더링하고 있는데 렌더링하는 함수(RenderScene()) 내부에서 ShapeFile 도형 정보(라인, 폴리곤) 등의 정보를 매 프레임마다 초기화 후 다시 렌더링하는 방식(Immediate Mode)으로 동작.

 즉, 매 프레임마다 동일한 도형을 반복적으로 매번 그리게 되어 특히 지도 이동, 확대/축소 명령 시 병목현상이 일어나는 것으로 파악(매 프레임마다 CPU → GPU로 렌더링 명령을 반복 전송)

 

해결 방법

OpenGL의 Display List 기능을 활용하여 자주 그리는 도형(Shapefile) 정보를 미리 컴파일해 저장해두고,
필요할 때 glCallList()로 빠르게 재사용하는 구조로 변경

1. Shapefile 데이터를 처음 불러올 때 도형별로 Display List를 생성

2. 렌더링 시점에 glCallList로 호출만하게 하여 성능 최적화

 

📈성능 최적화 효과

항  목 변경 전 변경 후
평균 렌더링 시간 (객체 70개 기준) 160~300ms 30~50ms
지도 이동 시 반응성 뚝뚝 끊김 부드러운 스크롤

 

void GraphicsManager::RenderScene(void)
{
 …(생략)
ShapefileGroup* pShapefileGroup = NULL;
ShapefileGroupVectorIter groupIter;
ShapefilePair* pShapefilePair = NULL;
ShapefilePairVectorIter pairIter;

groupIter = m_ShapefileGroupVector.begin();
while (groupIter != m_ShapefileGroupVector.end())
{
	pShapefileGroup = (ShapefileGroup*)*groupIter;

	if (pShapefileGroup && pShapefileGroup->isVisible)
	{
		pairIter = pShapefileGroup->shapefilePairVector.begin();
		while (pairIter != pShapefileGroup->shapefilePairVector.end())
		{

			pShapefilePair = (ShapefilePair*)*pairIter;
			if (pShapefilePair)
			{
				if (!pShapefilePair->displayListCreated)
				{
					BuildShapefileDisplayList(pShapefilePair); // Display List 생성
				}

				::glColor3f(pShapefilePair->color.r / 255.0f * m_Brightness,
					pShapefilePair->color.g / 255.0f * m_Brightness,
					pShapefilePair->color.b / 255.0f * m_Brightness);

				//RenderShapefile(pShapefilePair->converted); → 기존 코드(여기서 매 프레임마다 렌더링 수행)
				RenderShapefileFromList(pShapefilePair); // Display List 내 렌더링된 지형정보 불러오기

			}
			++pairIter;
		}

	}
	++groupIter;
}
 …(생략)
}

void GraphicsManager::BuildShapefileDisplayList(ShapefilePair* pair)
{
	if (!pair || pair->displayListCreated || !pair->converted.records || pair->converted.numRecords <= 0)
		return;

	pair->glDisplayList = glGenLists(1); 
	glNewList(pair->glDisplayList, GL_COMPILE); // 새로운 DisplayList 생성
	
	// 기존 RenderShapefile(pShapefilePair->converted)을 여기에서 수행
	for (int recNum = 0; recNum < pair->converted.numRecords; ++recNum)
	{
		SHAPEFILE_RECORD* recPtr = pair->converted.records[recNum];
		if (!recPtr) continue;

		if (recPtr->shapeType == SHAPE_TYPE_POINT)
		{
		}
		else
		{
			for (int partNum = 0; partNum < recPtr->numParts; ++partNum)
			{
				SHAPEFILE_PART& part = recPtr->parts[partNum];

				// 타입별로 glBegin 설정
				if (recPtr->shapeType == SHAPE_TYPE_POLYGON || recPtr->shapeType == SHAPE_TYPE_POLYLINE)
				{
					glBegin(GL_LINE_STRIP);
				}
				else
				{
					switch (part.partType)
					{
					case SHAPE_PARTTYPE_TRIANGLE_STRIP:
						glBegin(GL_TRIANGLE_STRIP);
						break;
					case SHAPE_PARTTYPE_TRIANGLE_FAN:
						glBegin(GL_TRIANGLE_FAN);
						break;
					case 6: // 삼각형
						glBegin(GL_TRIANGLES);
						break;
					default:
						continue;
					}
				}

				for (int i = 0; i < part.numPoints; ++i)
				{
					glVertex2d(part.points[i].x, part.points[i].y);
				}

				glEnd();
			}
		}
	}

void GraphicsManager::RenderShapefileFromList(ShapefilePair* pair)
{
	if (!pair || !pair->displayListCreated)
		return;

	glCallList(pair->glDisplayList);
}