개발/OpenGL / / 2022. 3. 19. 14:09

OpenGL 공부일지 - OpenGL Super Bible 쉐이더와 프로그램 - 3

반응형

Shader Subroutines

프로그램이 분리 모드로 링크되는 경우에도 프로그램 객체 간 전환은 성능면에서 비용이 많이 드므로 대신 서브루틴 유니폼을 사용할 수 있다. C의 함수 포인터와 유사하게 동작하는 유니폼 타입이다. 

 

// eg6-5.cpp
#version 450 core

// First, declare the subroutine type
subroutine vec4 sub_mySubroutine(vec4 param1);

// Next, declare a couple of functions that can be used as subroutines...
subroutine (sub_mySubroutine)
vec4 myFunction1(vec4 param1)
{
	return param1 * vec4(1.0, 0.25, 0.25, 1.0);
}

subroutine (sub_mySubroutine)
vec4 myFunction2(vec4 param1)
{
	return param1 * vec4(0.25, 0.25, 1.0, 1.0);
}

// Finally, declare a subroutine uniform that can be "pointed"
// at subroutine functions matching its signature
subroutine uniform sub_mySubroutine mySubroutineUniform;

// Output color
out vec4 color;

void main(void)
{
    // Call subroutine through uniform
	color = mySubroutineUniform(vec4(1.0));
}

위 코드는 서브루틴 유니폼을 선언하는 예제이다. 서브루틴 유니폼을 사용하기 위해 서브루틴 타입을 선언하고, 호환 가능한 서브루틴을 선언 후 서브루틴 유니폼을 함수에 연결시킨다.

 

layout (index = 2)
subroutine (sub_mySubroutine)
vec4 myFunction1(vec4 param1)
{
	return param1 * vec4(1.0, 0.25, 0.25, 1.0);
}

layout (index = 1);
subroutine (sub_mySubroutine)
vec4 myFunction2(vec4 param1)
{
	return param1 * vec4(0.25, 0.25, 1.0, 1.0);
}

위처럼 인덱스 레이아웃 지시어를 사용해서 인덱스를 직접 쉐이더 코드에서 할당할 수도 있다.(glsl 430이상)

GLuint glGetProgramResourceIndex(GLuint program,
    GLenum programInterface,
    const char * name);

위 함수를 통해 인덱스가 어떤 것을 가리키는지 알 수 있다.

void glGetProgramResourceName(GLuint program,
    GLenum programInterface,
    GLuint index,
    GLsizei bufSize,
    GLsizei * length,
    char * name);

프로그램에 서브루틴의 인덱스가 주어졌을 때, 위 함수로 그 이름을 확인할 수 있다.

void glGetProgramStageiv(GLuint program,
    GLenum shadertype,
    GLenum pname,
    GLint *values);

위 함수를 통해 프로그램의 특정 스테이지에서 활성화된 서브루틴의 개수를 알 수 있다.

void glUniformSubroutinesuiv(GLenum shadertype,
    GLsizei count,
    const GLunit *indices);

위 함수는 프로그램의 서브루틴 이름들을 알았을때 해당 값을 설정하는 역할을 한다.

 

서브루틴 유니폼이 다른 유니폼과 다른 점을 알아보자.

  • 서브루틴 유니폼의 상태는 프로그램 객체에 저장되기보다 현재 opengl 콘텍스트에 저장된다. 다른 콘텍스트에서 사용되는 경우에는 프로그램 객체가 같더라도 다른 값을 가질 수 있다.
  • 서브루틴 유니폼의 값은 새로운 프로그램을 사용하거나 새로운 프로그램 스테이지를 사용할 때 마다 매번 리셋해야한다.
  • 프로그램 객체의 스테이지 내 서브루틴 유니폼들 중 일부만 값을 변경하는 것은 불가능하다. 
subroutines[0] = glGetProgramResourceIndex(render_program,
    GL_FRAGMENT_SHADER_SUBROUTINE,
    "myFunction1");
subroutines[1] = glGetProgramResourceIndex(render_program,
    GL_FRAGMENT_SHADER_SUBROUTINE,
    "myFunction2");

프로그램 객체를 링크한 다음, 위 코드를 수행하면, 쉐이더에서 명시적으로 위치를 할당하지 않는 경우에도 서브루틴 함수들의 인덱스 확인이 가능하다.

void subroutines_app::render(double currentTime)
{
    int i = (int)currentTime;
    
    glUseProgram(render_program);
    
    glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &subroutines[i & 1]);
    
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

위 함수를 통해 프로그램 객체에 링크되어 있는 간단한 버텍스 쉐이더를 사용해서 사각형을 그린다. 

glUseProgram()으로 현재 프로그램을 설정하고, 프로그램의 서브루틴 유니폼 값들만 설정하였다. 모든 서브루틴 유니폼의 값은 프로그램 변경시 사라진다. 유니폼을 가리키는 서브루틴은 매 초마다 달라지므로 위의 eg6-5 프래그먼트 코드 사용시 화면이 1초마다 색이 빨간색에서 파란색 다시 빨간색으로 반복된다.

Program Binaries

프로그램 컴파일 및 링크를 끝냈다면, opengl에서는 프로그램의 내부 버전에 해당하는 바이너리 객체를 요청할 수 있다. 바이너리는 opengl에 다시 넘겨주면 컴파일 및 링크 과정없이 진행하는 것도 가능하다.

glProgramParameteri()함수의 설정으로 바이너리를 달라고 요청할 수 있다.

void glGetProgramBinary(GLuint program,
    GLsizei bufsize,
    GLsizei * length,
    GLenum * binaryFormat,
    void * binary);

위 함수를 호출하여 프로그램 객체의 바이너리 형태 데이터를 실제로 받을 수 있다.

 

// Create a simple program containing only a vertex shader
static const GLchar source[] = { ... };

// First create and compile the shader
GLuint shader;
shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, 1, suorce, NULL);
glCompileShader(shader);

// Create the program and attach the shader to it
GLuint program;
program = glCreateProgram();
glAttachShader(program, shader);

// Set the binary retrievable hint and link the program
glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
glLinkProgram(program);

// Get the expected size of the program binary
GLint binary_size = 0;
glGetProgramiv(program, GL_PROGRAM_BINARY_SIZE, &binary_size);

// Allocate some memory to store the program binary
unsigned char * program_binary = new unsigned char [binary_size];

// Now retrieve the binary from the program object
GLenum binary_format = GL_NONE;
glGetProgramBinary(program, binary_size, NULL, &binary_format,
	program_binary);

위 코드는 opengl로부터 프로그램 바이너리를 요청하는 방법의 예시이다. 

바이너리를 받으면 디스크에 저장할 수도 있고, 다음 프로그램 시작 시 사용하는 것도 가능하다. 그러면 렌더링 시작 전 쉐이더 컴파일과 프로그램 링크 시간을 절약할 수 있다. 

 

프로그램 바이너리를 opengl에 전달할 준비가 되면, 새로 만든 프로그램 객체에 대해 binaryFormat을 지정하고, glGetProgramBinary()를 호출하여 받은 값을 length로 설정한다. 그 다음 바이너리 형태의 버퍼로 데이터를 로드하여 glProgramBinary()를 호출한다. 이렇게 함으로써 프로그램 객체는 지난 애플리케이션 실행 시 요청한 바이너리로 프로그램 객체를 재로딩한다. 만약 실패한다면 쉐이더에 대한 원본 GLSL 소스를 사용해서 재컴파일한다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유