OpenGLES Programming Guide for iOS 번역 - 11장. 쉐이더를 위한 모범적 방법
본 번역은 얼마든지 정확하지 않을 수 있습니다. 참고용으로만 보시길... ^^;;;
쉐이더는 응용 프로그램에 높은 유연성을 제공하지만 많은 계산을 수행하거나 너무 계산 효율이 나쁘거나 하면 큰 병목이 될 수 있습니다.
초기화하는 동안 쉐이더 컴파일 및 링크
쉐이더 프로그램의 작성은 OpenGL ES의 다른 상태 변경에 비해 비용이 많이 드는 작업입니다. 목록 11-1는 쉐이더 프로그램을 로드, 컴파일 및 검증하기위한 전형적인 방법을 보여줍니다.
목록 11-1. 쉐이더로드
/** Initialization - time for shader **/
GLuint shader, prog;
GLchar * shaderText = "... shader text ...";
// Create ID for shader
shader = glCreateShader(GL_VERTEX_SHADER);
// Define shader text
glShaderSource(shaderText);
// Compile shader
glCompileShader(shader);
// Associate shader with program
glAttachShader(prog, shader);
// Link program
glLinkProgram(prog);
// Validate program
glValidateProgram(prog);
// Check the status of the compile / link
glGetProgramiv(prog, GL_INFO_LOG_LENGTH & logLen);
if(logLen> 0)
{
// Show any errors as appropriate
glGetProgramInfoLog(prog, logLen & logLen, log);
fprintf(stderr, "Prog Info Log : %s\n", log);
}
// Retrieve all uniform locations that are determined during link phase
for(i = 0; i < uniformCt; i ++)
{
uniformLoc [i] = glGetUniformLocation(prog, uniformName);
}
// Retrieve all attrib locations that are determined during link phase
for(i = 0; i < attribCt; i ++)
{
attribLoc [i] = glGetAttribLocation(prog, attribName);
}
/** Render stage for shaders **/
glUseProgram(prog);
응용 프로그램을 초기화할 때 프로그램의 컴파일, 링크 및 검증을 실시합니다. 모든 쉐이더를 만든 후 응용 프로그램은 glUseProgram를 호출하여 효율적으로 그들을 전환할 수 있습니다.
쉐이더에 대한 하드웨어 제한 사항을 준수
OpenGL ES는, 정점 셰이더 또는 프래그먼트 쉐이더에서 사용할 수있는 각 변수 유형의 수가 제한되어 있습니다. OpenGL ES구현은 이러한 제한을 초과하는 경우 소프트웨어 대체를 구현할 필요가 없습니다. 대신 쉐이더는 컴파일 또는 링크에 실패합니다. 응용 프로그램은 모든 쉐이더를 확인하여 컴파일하는 동안 오류가 발생하지 않았다는 것을 확인하는 필요가 있습니다(목록 11-1 참조).
정확도 힌트를 사용
임베디드 장치의 엄격한 하드웨어 제한에 적합한 컴팩트한 쉐이더 변수의 요구에 대응하기 위해 GLSL ES 언어 사양 정밀도 힌트가 추가되었습니다. 각 쉐이더는 기본 정밀도를 정의해야 합니다. 개별 쉐이더 변수는이 정밀도를 재정의하고 그 변수 응용 프로그램의 사용 방법을 컴파일러에 힌트를 제공할 수 있습니다. OpenGL ES 의 구현에서는, 팁 정보를 사용할 필요는 없지만,보다 효율적인 쉐이더를 생성하기위한 팁 정보를 사용하는 것은 가능합니다. GLSL ES 사양은 각 정보의 범위와 정밀도의 목록이 나와 있습니다.
중요 : 정확도 힌트 정의되고있는 범위 제한은 강제가 없습니다. 따라서 데이터가 이 범위에 들어가고 있다고 가정 할 수 없습니다.
iOS 응용프로그램의 경우 다음 가이드를 따르십시오.
● 헷갈리는 경우 정밀도를 기본값으로 합니다.
● 0.0 ~ 1.0 범위의 색상은 일반적으로 낮은 정밀도의 변수를 사용하여 표현할 수 있습니다.
● 위치 데이터는 일반적으로 높은 정밀도로 저장합니다.
● 조명 계산에 사용되는 법선과 벡터는 일반적으로 중간 정도의 정밀도로 저장할 수 있습니다.
● 정밀도를 내린 후 응용 프로그램을 다시 테스트하여 결과가 예상대로 되도록 합니다.
목록 11-2 정밀 변수가 기본값이지만, 높은 정밀도는 필요 없기 때문에 낮은 정밀도의 변수를 사용하여 색상 출력을 계산합니다.
목록 11-2. 조각 색상 낮은 정밀도를 사용
default precision highp // 조각 셰이더에서는 기본 정밀도 선언이 필요하다
uniform lowp sampler2D sampler; // Texture2D() 의 결과는 낮은 정도
varying lowp vec4 color;
varying vec2 texCoord // 기본 정밀도를 사용
void main()
{
gl_FragColor = color * texture2D(sampler, texCoord);
}
벡터 계산시 시간이 걸리는 실행
모든 그래픽 프로세서 벡터 프로세서가 포함되어있는 것은 아니고, 스칼라 프로세서에서 벡터 계산을 수행하는 그래픽 프로세서도 있습니다. 쉐이더에서 계산을 수행하는 경우는 스칼라 프로세서에서 계산이 수행되는 경우에도 효율적으로 실행되는 있도록, 연산의 순서를 고려합니다.
목록 11-3 의 코드 벡터 프로세서에서 실행되는 경우 각 곱셈은 4개의 벡터의 모든 구성 요소에 걸쳐 동시에 실행됩니다. 그러나 괄호의 위치를 이유로 3개의 매개 변수 중 2개가 스칼라에도 프로세서에서 동일한 연산 8번의 곱셈을 요할 수 있습니다.
목록 11-3. 벡터 연산의 비적절한 사용
highp float f0, f1;
highp vec4 v0, v1;
v0 = (v1 * f0) * f1;
목록 11-4 과 같이 괄호를 이동하여 같은 계산을보다 효율적으로 수행할 수 있습니다. 이 예제에서는 스칼라 값을 먼저 곱한 결과를 벡터 파라미터로 곱합니다. 전체 연산은 5 개의 곱셈 계산할 수 있습니다.
목록 11-4. 벡터 연산의 적절한 사용
highp float f0, f1;
highp vec4 v0, v1;
v0 = v1 * (f0 * f1);
마찬가지로 응용 프로그램에서 결과의 모든 구성 요소를 사용하는 것은 않으면 반드시 벡터 연산에 쓰기 마스크를 지정하십시오. 스칼라 프로세서에서는 마스크로 지정되지 않은 구성 요소의 계산은 건너뛸 수 있습니다. 목록 11-5 은 2가지 구성 요소를 필요로 지정하고 있기 때문에 스칼라 프로세서에서는 2배의 속도로 실행됩니다.
목록 11-5. 쓰기 마스크 지정
highp vec4 v0;
highp vec4 v1;
highp vec4 v2;
v2.xz = v0 * v1;
쉐이더에서 계산 대신 uniform 또는 constant를 사용
쉐이더 외부에서 값을 계산할 수있는 경우는 값을 uniform 또는 constant로 쉐이더로 전달합니다. 동적 값을 다시 계산하면 셰이더에서는 매우 부하가 커질 가능성이 있습니다.
분기를 피하기
쉐이더에서 분기는 권장되지 않습니다. 3D 그래픽 프로세서에서 병렬 연산을 수행하는 능력이 저하될 수 있기 때문입니다. 쉐이더 분기를 사용해야 할 경우 다음의 권장 사항을 따르십시오.
● 최고의 성능 : 셰이더의 컴파일 시에 알려진 constant에 분기한다.
● 허용 : uniform 변수에 분기한다.
● 처리 속도가 느려질 수 있음 : 쉐이더에서 계산된 값에 분기한다.
많은 노브(knobs)와 레버(levers)를 가진 큰 쉐이더를 만드는 대신 구체적인 렌더링 작업에 특화한 작은 쉐이더를 만듭니다. 쉐이더의 분기 수를 줄이거나 만들 쉐이더의 수를 늘리는 것은 트레이드 오프입니다. 다양한 옵션을 테스트하고 가장 빠른 것을 선택합니다.
루프를 제거
루프를 확장하거나 벡터를 사용하여 연산을 수행하면 많은 루프를 없앨 수 있습니다. 예를 들어, 다음 코드는 매우 비효율적입니다.
// 루프
int i;
float f;
vec4 v;
for(i = 0; i < 4; i + +)
v[i] + = f;
같은 연산을 구성 요소 수준의 가산을 직접 사용하여 수행할 수 있습니다.
float f;
vec4 v;
v += f;
루프를 배제할 수없는 경우 루프 상수 상한을 두고 동적 분기를 피하는 것이 좋습니다.
참고 : iOS 4.2 이상에서는 셰이더 컴파일러는 보다 적극적으로 루프 최적화합니다.
쉐이더의 배열 인덱스 계산을 피하기
쉐이더에서 계산된 인덱스를 사용하면 constant 또는 uniform 배열 인덱스를 사용하는 것보다 비용이 많이 듭니다. 일반적 uniform 배열에 액세스 임시 배열에 접근 비해 비용은 들지 않습니다.
동적 텍스처 검색을 피하기
동적 텍스처 검색, 종속 텍스처 읽기라고도 알려진 조각 쉐이더, 쉐이더에 전달된 수정되지 않은 텍스처 좌표를 사용하는 것이 아니라, 텍스처 좌표를 계산하는 경우에 발생합니다. 쉐이더 언어는 이 계산을 지원하고 있습니다만, 종속 텍스처 읽기에서는 텍셀 데이터로드가 지연되어 성능이 저하될 우려가 있습니다. 쉐이더가 종속 텍스처 읽기를 하지 않는 경우는 그래픽 하드웨어 쉐이더를 실행하기 전에 텍셀 데이터를 사전에 검색하고 메모리 액세스 대기 시간을 느끼게 하지 않을 수 있습니다.
목록 11-6 새로운 텍스처 좌표를 계산하는 프래그먼트 쉐이더를 보여줍니다. 이 예제의 계산은 정점 셰이더에서는 쉽게 수행할 수 있습니다. 계산을 정점 쉐이더로 이동하여 정점 셰이더가 계산하는 텍스처 좌표를 직접 사용하여 응용 프로그램이 종속 텍스처 읽기를 피할 수 있습니다.
Note : 이해하기 어려울지도 모릅니다만, 텍스처 좌표에 대한 계산은 모두 종속 텍스처 읽기 분류로 계산됩니다. 예를 들어, 텍스처 좌표의 여러 집합을 1개의 varying 매개 변수 감싸고 swizzle 명령을 사용하여 텍스처 좌표를 추출해서 종속 텍스처 읽기가 발생합니다.
목록 11-6. 종속 텍스처 읽기
varying vec2 vTexCoord;
uniform sampler textureSampler;
void main()
{
vec2 modifiedTexCoord = vec2(1.0 - vTexCoord.x, 1.0 - vTexCoord.y);
gl_FragColor = texture2D(textureSampler, modifiedTexCoord);
}