ofVbo#drawXxxの実装
シェーダ周りで気になることがいくつかあったので調べてみました。
基準となるパスは openframeworks/libs/openframeworks
です。
- どういう場合にデフォルトシェーダが設定されるかを確認します。
- シェーダにデフォルトで設定される変数を確認します。
バージョン
of_v0.10.1_vs2017_release
調査内容
依存関係の浅いところから並べています。
./gl/ofVbo.cpp
drawXxx()
ofGetGLRenderer() を読んでいく
//--------------------------------------------------------------
void ofVbo::draw(int drawMode, int first, int total) const{
ofGetGLRenderer()->draw(*this,drawMode,first,total);
}
//--------------------------------------------------------------
void ofVbo::drawElements(int drawMode, int amt, int offsetelements) const{
ofGetGLRenderer()->drawElements(*this,drawMode,amt,offsetelements);
}
//--------------------------------------------------------------
void ofVbo::drawInstanced(int drawMode, int first, int total, int primCount) const{
ofGetGLRenderer()->drawInstanced(*this,drawMode,first,total,primCount);
}
//--------------------------------------------------------------
void ofVbo::drawElementsInstanced(int drawMode, int amt, int primCount) const{
ofGetGLRenderer()->drawElementsInstanced(*this,drawMode,amt,primCount);
}
./gl/ofGLUtils.cpp
ofGetGLRenderer()
環境によってレンダラを切り替えられる模様
プログラマブルシェーダでないときに
上の実装が使われる
# ifndef TARGET_PROGRAMMABLE_GL
shared_ptr<ofBaseGLRenderer> ofGetGLRenderer(){
if(ofGetCurrentRenderer()->getType()==ofGLRenderer::TYPE || ofGetCurrentRenderer()->getType()==ofGLProgrammableRenderer::TYPE){
return (shared_ptr<ofBaseGLRenderer>&)ofGetCurrentRenderer();
}else if(ofGetCurrentRenderer()->getType()==ofRendererCollection::TYPE){
return ((shared_ptr<ofRendererCollection>&)ofGetCurrentRenderer())->getGLRenderer();
}else{
return shared_ptr<ofGLRenderer>();
}
}
# else
shared_ptr<ofBaseGLRenderer> ofGetGLRenderer(){
if(ofGetCurrentRenderer()->getType()==ofGLProgrammableRenderer::TYPE){
return (shared_ptr<ofBaseGLRenderer>&)ofGetCurrentRenderer();
}else if(ofGetCurrentRenderer()->getType()==ofRendererCollection::TYPE){
return ((shared_ptr<ofRendererCollection>&)ofGetCurrentRenderer())->getGLRenderer();
}else{
return shared_ptr<ofGLProgrammableRenderer>();
}
}
# endif
./utils/ofConstants.h
筆者はWindows10を使っているので TARGET_WIN32 だけが定義される。
なので、先程のメソッドは上のものが使用される。
// helpful:
// http://www.ogre3d.org/docs/api/html/OgrePlatform_8h-source.html
# if defined( __WIN32__ ) || defined( _WIN32 )
#define TARGET_WIN32
# elif defined( __APPLE_CC__)
#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
#include <TargetConditionals.h>
#if (TARGET_OS_IPHONE || TARGET_OS_IOS || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE_SIMULATOR) && !TARGET_OS_TV && !TARGET_OS_WATCH
#define TARGET_OF_IPHONE
#define TARGET_OF_IOS
#define TARGET_OPENGLES
#include <unistd.h>
#elif TARGET_OS_TV
#define TARGET_OF_IOS
#define TARGET_OF_TVOS
#define TARGET_OPENGLES
#include <unistd.h>
#elif TARGET_OS_WATCH
#define TARGET_OF_IOS
#define TARGET_OF_WATCHOS
#define TARGET_OPENGLES
#include <unistd.h>
#else
#define TARGET_OSX
#endif
# elif defined (__ANDROID__)
#define TARGET_ANDROID
#define TARGET_OPENGLES
# elif defined(__ARMEL__)
#define TARGET_LINUX
#define TARGET_OPENGLES
#define TARGET_LINUX_ARM
# elif defined(__EMSCRIPTEN__)
#define TARGET_EMSCRIPTEN
#define TARGET_OPENGLES
#define TARGET_NO_THREADS
#define TARGET_PROGRAMMABLE_GL
#define TARGET_IMPLEMENTS_URL_LOADER
# else
#define TARGET_LINUX
# endif
./gl/ofGLUtils.cpp(再掲)
ofGetGLRenderer()
ofGetCurrentRenderer() を読んでいく
# ifndef TARGET_PROGRAMMABLE_GL
shared_ptr<ofBaseGLRenderer> ofGetGLRenderer(){
if(ofGetCurrentRenderer()->getType()==ofGLRenderer::TYPE || ofGetCurrentRenderer()->getType()==ofGLProgrammableRenderer::TYPE){
return (shared_ptr<ofBaseGLRenderer>&)ofGetCurrentRenderer();
}else if(ofGetCurrentRenderer()->getType()==ofRendererCollection::TYPE){
return ((shared_ptr<ofRendererCollection>&)ofGetCurrentRenderer())->getGLRenderer();
}else{
return shared_ptr<ofGLRenderer>();
}
}
# else
...
中略
...
# endif
./app/ofAppRunner.cpp
ofGetCurrentRenderer()
getCurrentWindow() を読んでいく
//--------------------------------------
shared_ptr<ofBaseRenderer> & ofGetCurrentRenderer(){
return mainLoop()->getCurrentWindow()->renderer();
}
./app/ofAppRunner.cpp
mainLoop()
メインループを生成して返す。
staticとついているので一度しか生成されない。
//--------------------------------------
namespace{
shared_ptr<ofMainLoop> & mainLoop(){
static shared_ptr<ofMainLoop> * mainLoop(new shared_ptr<ofMainLoop>(new ofMainLoop));
return *mainLoop;
}
中略
}
./app/ofMainLoop.cpp
getCurrentWindow()
ofMainLoopのコンストラクタではcurrentWindow
を設定していないので、setしているところを探す。
shared_ptr<ofAppBaseWindow> ofMainLoop::getCurrentWindow(){
return currentWindow.lock();
}
./app/ofMainLoop.cpp
setCurrentWindow()
void ofMainLoop::setCurrentWindow(shared_ptr<ofAppBaseWindow> window){
currentWindow = window;
}
void ofMainLoop::setCurrentWindow(ofAppBaseWindow * window){
if(currentWindow.lock().get() == window){
return;
}
for(auto i: windowsApps){
if(i.first.get() == window){
currentWindow = i.first;
break;
}
}
}
./app/ofAppGLFWWindow.cpp
setCurrent()
setCurrentWindow() を読んでいるのはここだけ。
実はofAppBaseGLWindow
のサブクラスにはofAppGlutWindow
というのもあるのですが、
こちらでは setCurrentWindow() を読んでいないみたいです。
こちらのリンク曰く、GLUTからGLFWに移行したとのことなのでこれであっているのでしょうか
であるなら、先程のmainLoop()->getCurrentWindow()->renderer()
はofAppGLFWWindow
の renderer() が呼ばれているはず。
//------------------------------------------------------------
ofAppGLFWWindow * ofAppGLFWWindow::setCurrent(GLFWwindow* windowP){
ofAppGLFWWindow * instance = static_cast<ofAppGLFWWindow *>(glfwGetWindowUserPointer(windowP));
shared_ptr<ofMainLoop> mainLoop = ofGetMainLoop();
if(mainLoop){
mainLoop->setCurrentWindow(instance);
}
instance->makeCurrent();
return instance;
}
./app/ofAppGLFWWindow
renderer()
currentRenderer
は setup() で初期化されます。
//--------------------------------------------
shared_ptr<ofBaseRenderer> & ofAppGLFWWindow::renderer(){
return currentRenderer;
}
setup()
実際にはかなり長いメソッドですが、currentRenderer
を初期化してる部分に絞って抜粋。
GLESか否かで分岐しますが、どちらもofGLProgrammableRenderer
かofGLRenderer
を選択するようです。
TARGET_OPENGLES
は ./utils/ofConstants.h
でも登場していますが、モバイル端末などで選択されるフラグのようです。
また、筆者の環境ではOpenGLのメジャーバージョンは4なのでofGLProgrammableRenderer
へ進みます。
void ofAppGLFWWindow::setup(const ofGLFWWindowSettings & _settings){
...
中略
...
glfwWindowHint(GLFW_SAMPLES, settings.numSamples);
glfwWindowHint(GLFW_RESIZABLE, settings.resizable);
glfwWindowHint(GLFW_DECORATED, settings.decorated);
#ifdef TARGET_OPENGLES
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, settings.glesVersion);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
if(settings.glesVersion>=2){
currentRenderer = std::make_shared<ofGLProgrammableRenderer>(this);
}else{
currentRenderer = std::make_shared<ofGLRenderer>(this);
}
#else
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, settings.glVersionMajor);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, settings.glVersionMinor);
if((settings.glVersionMajor==3 && settings.glVersionMinor>=2) || settings.glVersionMajor>=4){
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}
if(settings.glVersionMajor>=3){
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
currentRenderer = std::make_shared<ofGLProgrammableRenderer>(this);
}else{
currentRenderer = std::make_shared<ofGLRenderer>(this);
}
#endif
...
中略
...
# endif
}
./gl/ofGLUtils.cpp(再掲)
ofGetGLRenderer()
ofGetCurrentRenderer() が ofGLProgrammableRenderer
を返すことがわかったので、
getType() の実装へ進みます。
# ifndef TARGET_PROGRAMMABLE_GL
shared_ptr<ofBaseGLRenderer> ofGetGLRenderer(){
if(ofGetCurrentRenderer()->getType()==ofGLRenderer::TYPE || ofGetCurrentRenderer()->getType()==ofGLProgrammableRenderer::TYPE){
return (shared_ptr<ofBaseGLRenderer>&)ofGetCurrentRenderer();
}else if(ofGetCurrentRenderer()->getType()==ofRendererCollection::TYPE){
return ((shared_ptr<ofRendererCollection>&)ofGetCurrentRenderer())->getGLRenderer();
}else{
return shared_ptr<ofGLRenderer>();
}
}
# else
...
中略
...
# endif
./gl/ofGLProgrammableRenderer.h
文字列を返すようです。
...
中略
...
static const std::string TYPE;
const std::string & getType(){ return TYPE; }
...
中略
...
./gl/ofGLProgrammableRenderer.cpp
ということで、
ofGetCurrentRenderer()->getType()==ofGLProgrammableRenderer::TYPE
は true を返します。
const string ofGLProgrammableRenderer::TYPE="ProgrammableGL";
./gl/ofGLUtils.cpp(再掲)
よって、呼び出されるのはこれです。
戻り値は ofGLProgrammableRenderer
return (shared_ptr<ofBaseGLRenderer>&)ofGetCurrentRenderer();
./gl/ofGLProgrammableRenderer.cpp
draw()
なので、ofVbo#draw はこれを呼んでいる。
//----------------------------------------------------------
void ofGLProgrammableRenderer::draw(const ofVbo & vbo, GLuint drawMode, int first, int total) const{
if(vbo.getUsingVerts()) {
vbo.bind();
const_cast<ofGLProgrammableRenderer*>(this)->setAttributes(vbo.getUsingVerts(),vbo.getUsingColors(),vbo.getUsingTexCoords(),vbo.getUsingNormals());
glDrawArrays(drawMode, first, total);
vbo.unbind();
}
}
setAttributes()
//----------------------------------------------------------
void ofGLProgrammableRenderer::setAttributes(bool vertices, bool color, bool tex, bool normals){
bool wasColorsEnabled = colorsEnabled;
bool wasUsingTexture = texCoordsEnabled & (currentTextureTarget!=OF_NO_TEXTURE);
texCoordsEnabled = tex;
colorsEnabled = color;
normalsEnabled = normals;
if(!uniqueShader || currentMaterial){
beginDefaultShader();
}
bool usingTexture = tex & (currentTextureTarget!=OF_NO_TEXTURE);
if(wasUsingTexture!=usingTexture){
if(currentShader) currentShader->setUniform1f(USE_TEXTURE_UNIFORM,usingTexture);
}
if(wasColorsEnabled!=color){
if(currentShader) currentShader->setUniform1f(USE_COLORS_UNIFORM,color);
}
}
beginDefaultShader()
ここでデフォルトで適用するシェーダを決める。
oFのデフォルトの Xxx#draw ではこのシェーダが使われていると思われる。
ofGetCurrentRenderer()->draw()
で検索すると、具体的にどのクラスで使われているかわかる。
※ ofGetCurrentRenderer() == ofGetGLRenderer()
であることはすでに分かっているため
//----------------------------------------------------------
void ofGLProgrammableRenderer::beginDefaultShader(){
if(usingCustomShader && !currentMaterial) return;
const ofShader * nextShader = nullptr;
if(!uniqueShader || currentMaterial){
if(currentMaterial){
nextShader = ¤tMaterial->getShader(currentTextureTarget,colorsEnabled,*this);
}else if(bitmapStringEnabled){
nextShader = &bitmapStringShader;
}else if(colorsEnabled && texCoordsEnabled){
switch(currentTextureTarget){
#ifndef TARGET_OPENGLES
case GL_TEXTURE_RECTANGLE_ARB:
nextShader = &defaultTexRectColor;
break;
#endif
case GL_TEXTURE_2D:
nextShader = &defaultTex2DColor;
break;
case OF_NO_TEXTURE:
nextShader = &defaultNoTexColor;
break;
#ifdef TARGET_ANDROID
case GL_TEXTURE_EXTERNAL_OES:
nextShader = &defaultOESTexColor;
break;
#endif
}
}else if(colorsEnabled){
nextShader = &defaultNoTexColor;
}else if(texCoordsEnabled){
switch(currentTextureTarget){
#ifndef TARGET_OPENGLES
case GL_TEXTURE_RECTANGLE_ARB:
nextShader = &defaultTexRectNoColor;
break;
#endif
case GL_TEXTURE_2D:
nextShader = &defaultTex2DNoColor;
break;
case OF_NO_TEXTURE:
nextShader = &defaultNoTexNoColor;
break;
#ifdef TARGET_ANDROID
case GL_TEXTURE_EXTERNAL_OES:
nextShader = &defaultOESTexNoColor;
break;
#endif
}
}else{
nextShader = &defaultNoTexNoColor;
}
}else{
nextShader = &defaultUniqueShader;
}
if(nextShader){
if(!currentShader || *currentShader!=*nextShader){
settingDefaultShader = true;
bind(*nextShader);
settingDefaultShader = false;
}
}
}
setAttributes()(再掲)
以下の条件を満たさなければ
カスタムしたシェーダを使えることがわかる。
//----------------------------------------------------------
void ofGLProgrammableRenderer::setAttributes(bool vertices, bool color, bool tex, bool normals){
...
中略
...
if(!uniqueShader || currentMaterial){
beginDefaultShader();
}
...
中略
...
}
./app/ofGLProgrammableRenderer.cpp
コンストラクタではfalseに設定されており、uniqueShader が trueになるのはここだけ。
ということは、ラズパイ環境でないかぎり必ず beginDefaultShader() は呼ばれてしまう。
# ifdef TARGET_RASPBERRY_PI
uniqueShader = true;
# else
uniqueShader = false;
# endif
beginDefaultShader()(再掲)
-
カスタムシェーダが使われている
||マテリアルがある
ときになにもしないことがわかる。 -
次のシェーダがある
&&現在のシェーダがある
&&現在のシェーダと次のシェーダが違う
ときにデフォルトシェーダがバインドされることがわかる。
//----------------------------------------------------------
void ofGLProgrammableRenderer::beginDefaultShader(){
if(usingCustomShader && !currentMaterial) return;
const ofShader * nextShader = nullptr;
if(!uniqueShader || currentMaterial){
if(currentMaterial){
nextShader = ¤tMaterial->getShader(currentTextureTarget,colorsEnabled,*this);
}else if(bitmapStringEnabled){
nextShader = &bitmapStringShader;
}else if(colorsEnabled && texCoordsEnabled){
switch(currentTextureTarget){
#ifndef TARGET_OPENGLES
case GL_TEXTURE_RECTANGLE_ARB:
nextShader = &defaultTexRectColor;
break;
#endif
case GL_TEXTURE_2D:
nextShader = &defaultTex2DColor;
break;
case OF_NO_TEXTURE:
nextShader = &defaultNoTexColor;
break;
#ifdef TARGET_ANDROID
case GL_TEXTURE_EXTERNAL_OES:
nextShader = &defaultOESTexColor;
break;
#endif
}
}else if(colorsEnabled){
nextShader = &defaultNoTexColor;
}else if(texCoordsEnabled){
switch(currentTextureTarget){
#ifndef TARGET_OPENGLES
case GL_TEXTURE_RECTANGLE_ARB:
nextShader = &defaultTexRectNoColor;
break;
#endif
case GL_TEXTURE_2D:
nextShader = &defaultTex2DNoColor;
break;
case OF_NO_TEXTURE:
nextShader = &defaultNoTexNoColor;
break;
#ifdef TARGET_ANDROID
case GL_TEXTURE_EXTERNAL_OES:
nextShader = &defaultOESTexNoColor;
break;
#endif
}
}else{
nextShader = &defaultNoTexNoColor;
}
}else{
nextShader = &defaultUniqueShader;
}
if(nextShader){
if(!currentShader || *currentShader!=*nextShader){
settingDefaultShader = true;
bind(*nextShader);
settingDefaultShader = false;
}
}
}
bind()
usingCustomShader
を true にしているのはここだけ。
//----------------------------------------------------------
void ofGLProgrammableRenderer::bind(const ofShader & shader){
if(currentShader && *currentShader==shader){
return;
}
glUseProgram(shader.getProgram());
currentShader = &shader;
uploadMatrices();
setDefaultUniforms();
if(!settingDefaultShader){
usingCustomShader = true;
}
}
./gl/ofShader.cpp
begin()
そして、ofShader#beginではこれを呼んでいるので
ちゃんとそのように書いていればシェーダは切り替わるはず。
ただし、glUseProgram(myShader);
のあとに ofObject#draw
とした場合には上書きされてしまう可能性がある。
//--------------------------------------------------------------
void ofShader::begin() const{
ofGetGLRenderer()->bind(*this);
}
./app/ofGLProgrammableRenderer.cpp
uploadMatrices(), setDefaultUniforms()
これは ofShader::bind
から呼ばれる。
デフォルトで定義されるシェーダ変数にどんな値が入っているかがわかる。
//----------------------------------------------------------
void ofGLProgrammableRenderer::uploadMatrices(){
if(!currentShader) return;
currentShader->setUniformMatrix4f(MODEL_MATRIX_UNIFORM, matrixStack.getModelMatrix());
currentShader->setUniformMatrix4f(VIEW_MATRIX_UNIFORM, matrixStack.getViewMatrix());
currentShader->setUniformMatrix4f(MODELVIEW_MATRIX_UNIFORM, matrixStack.getModelViewMatrix());
currentShader->setUniformMatrix4f(PROJECTION_MATRIX_UNIFORM, matrixStack.getProjectionMatrix());
currentShader->setUniformMatrix4f(TEXTURE_MATRIX_UNIFORM, matrixStack.getTextureMatrix());
currentShader->setUniformMatrix4f(MODELVIEW_PROJECTION_MATRIX_UNIFORM, matrixStack.getModelViewProjectionMatrix());
if(currentMaterial){
currentMaterial->uploadMatrices(*currentShader,*this);
}
}
//----------------------------------------------------------
void ofGLProgrammableRenderer::setDefaultUniforms(){
if(!currentShader) return;
currentShader->setUniform4f(COLOR_UNIFORM, currentStyle.color.r/255.,currentStyle.color.g/255.,currentStyle.color.b/255.,currentStyle.color.a/255.);
bool usingTexture = texCoordsEnabled & (currentTextureTarget!=OF_NO_TEXTURE);
currentShader->setUniform1f(USE_TEXTURE_UNIFORM,usingTexture);
currentShader->setUniform1f(USE_COLORS_UNIFORM,colorsEnabled);
if(currentMaterial){
currentMaterial->updateMaterial(*currentShader,*this);
currentMaterial->updateLights(*currentShader,*this);
}
}
./gl/ofShader.cpp
setUniformMatrix4f()
デフォルトの変数が見つからなかった場合にはちゃんと何もしないようになっている。
(他のsetUniformXxxも同様)
//--------------------------------------------------------------
void ofShader::setUniformMatrix4f(const string & name, const glm::mat4 & m, int count) const{
if(bLoaded) {
int loc = getUniformLocation(name);
if (loc != -1) glUniformMatrix4fv(loc, count, GL_FALSE, glm::value_ptr(m));
}
}
ライセンス
ここで転載したソースコードはMITライセンスで公開されています。
全文