Skip to content

Instantly share code, notes, and snippets.

@ykst
Created January 28, 2015 14:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ykst/8557e2ce9280111c41b6 to your computer and use it in GitHub Desktop.
Save ykst/8557e2ce9280111c41b6 to your computer and use it in GitHub Desktop.
glsl-optimizerでGLSLのオフライン最適化を行う

glsl-optimizerでGLSLのオフライン最適化を行う

OpenGLにコンパイルしてもらう時点でシェーダにはある程度最適化が行われるようですが、 モバイルデバイスではあまりコストの掛かる最適化は行われないだろうという事で、 オフラインでGLSLの最適化をしてくれるフレームワークとしてglsl-optimizerがあります。 サポートされているGLSLバージョンはES2.0とES3.0もカバーしていますが、未対応の拡張もあります。

元々はUnityが機械生成した冗長なGLSLを最適化するために作られたようですが、 一応手書きのシェーダの最適化に使うこともできます。 ただし、組み込みを前提としているようでコマンドラインからキックするバイナリは提供されていません。 とりあえずここでは、staticライブラリをビルドしてスタンドアローンで実行するまでのコードを紹介します。

ライブラリのビルド

glsl-optimizerをgit cloneして、 projects/xcode5/glsl_optimizer_lib.xcodeprojをXcodeで開き、そのままビルドしてC++ライブラリのlibglsl_optimizer.aを生成します。

ラッパーの作成

glsl_optimizer.hlibglsl_optimizer.aをコピーしてきて、適当にmain関数を書きます。

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <getopt.h>
#include <sys/stat.h>

#include "glsl_optimizer.h"

int main (int argc, char **argv)
{
    glslopt_shader_type shader_type = kGlslOptShaderFragment;

    int ch;
    while ((ch = getopt( argc, argv, "vf")) != -1 ){
        switch (ch) {
            case 'v':
                shader_type = kGlslOptShaderVertex;
                break;
            case 'f':
                shader_type = kGlslOptShaderFragment;
                break;
        }
    }
    argc -= optind;
    argv += optind;

    if (argc != 1) exit(1);

    struct stat st;
    stat(*argv, &st);
    size_t fsize = st.st_size;

    if (fsize == 0) exit(1);

    FILE *fp = fopen(*argv, "r");

    if (!fp) exit(1);

    char *shader_source = (char *)malloc(fsize+1);

    if (!shader_source) exit(1);

    shader_source[fsize] = '\0';

    if (fread(shader_source, fsize, 1, fp) != 1) exit(1);

    glslopt_ctx *ctx = glslopt_initialize(kGlslTargetOpenGLES20);
    glslopt_shader *shader = glslopt_optimize(ctx, shader_type, shader_source, 0);

    free(shader_source);

    int exit_code = 1;

    if (glslopt_get_status(shader)) {
        const char *newSource = glslopt_get_output(shader);
        fprintf(stdout, "%s", newSource);
        exit_code = 0;
    } else {
        const char *errorLog = glslopt_get_log(shader);
        fprintf(stderr, "%s", errorLog);
        exit_code = 1;
    }

    glslopt_shader_delete(shader);

    glslopt_cleanup(ctx);

    exit(exit_code);
}

Makefile。

TARGET=glsl_optimizer
CC=g++
CFLAGS= -O2 -Wall
SRCS=$(wildcard *.cpp)
OBJS=$(SRCS:%.cpp=%.o)
STATICLIBS=libglsl_optimizer.a
HEADERS=$(wildcard *.h)
LDFLAGS= 

all: $(TARGET)

$(TARGET): $(OBJS)
	    $(CC) $(CFLAGS) -o $@ $^ $(STATICLIBS) $(LDFLAGS)

%o: %c $(HEADERS)
	    $(CC) $(CFLAGS) -c $<

clean: 
	    $(RM) $(OBJS) $(TARGET)

このサンプルの場合は、シェーダ種別のフラグとファイルパスを与えて成功すればstdoutに変換結果を吐きます。

  • 頂点シェーダ
$ ./glsl_optimizer -v /pass/to/shader/vs.glsl
  • フラグメントシェーダ
$ ./glsl_optimizer -f /pass/to/shader/fs.glsl

スタンドアローンにしておけば、XcodeのCustom Buid Ruleでシェーダをバイナリに突っ込む際に変換するパスを挟む事も可能です。

今回のサンプルの場合はバイナリを/usr/local/binにインストールしたとして次のようなスクリプトになります。

if [[ "$INPUT_FILE_NAME" == *_vs.glsl ]]; then
  glsl_optimizer_opt="-v"
elif [[ "$INPUT_FILE_NAME" == *_fs.glsl ]]; then
  glsl_optimizer_opt="-f"
fi

cd "$INPUT_FILE_DIR" 

/usr/local/bin/glsl_optimizer ${glsl_optimizer_opt} "$INPUT_FILE_NAME" > "$DERIVED_SOURCES_DIR/$INPUT_FILE_BASE.glsl" || exit 1

cd "$DERIVED_SOURCES_DIR"
/usr/bin/xxd -i "$INPUT_FILE_BASE.glsl" | sed s/}\;/,0x00}\;/ > "$INPUT_FILE_BASE.glsl.c"

手書きのシェーダの場合は大体頭打ちなのであまりパフォーマンス向上を実感出来ないかもしれませんが、 非効率な書き方をしている箇所をフィードバックしてもらう用途にも使えますね。

参考

http://aras-p.info/blog/2010/09/29/glsl-optimizer/

@ykst
Copy link
Author

ykst commented Sep 27, 2015

Metalの時代ではオフラインコンパイラが付くので、こうした最適化は過去の遺物となるだろう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment