Help needed binding textures on AMD cards


#1

Hi,
I’ve recently noticed some unknown behaviour on an AMD Card. Previously I’ve been using nvidia cards & everythings worked absolutely fine, but having swapped to an AMD Radeon R9 270x (latest catalyst drivers), I’ve been having nothing but trouble.

The current thing that’s proving difficult is binding multiple textures to a shader. On nvidia this has been working perfectly for me in an app, but now I can’t seem to get it to respect the binds. I’ve distilled it to a super basic working app case, and still no luck;
Cinder Test App:

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"

using namespace ci;
using namespace ci::app;
using namespace std;

class MultiBindApp : public App {
  public:
	void setup() override;
	void update() override;
	void draw() override;
	void LoadShaders();
	gl::GlslProgRef Multibind;
	gl::Texture2dRef TexA;
	gl::Texture2dRef TexB;

};

void MultiBindApp::LoadShaders() {
	Multibind = gl::GlslProg::create(loadAsset("mbind.vert"), loadAsset("mBind.frag"));

}

void MultiBindApp::setup()
{
	TexA = gl::Texture2d::create(loadImage(getAssetPath("ImageA.png")));
	TexB = gl::Texture2d::create(loadImage(getAssetPath("ImageB.png")));
	LoadShaders();
}
	
void MultiBindApp::update()
{
	LoadShaders();
}

void MultiBindApp::draw()
{
	gl::clear( Color( 0, 0, 0 ) ); 

	Multibind->bind();

		TexA->bind(3);
		Multibind->uniform("TextureA", 3 );
		TexB->bind(4);
		Multibind->uniform("TextureB", 4);

	gl::drawSolidRect(Rectf(0.0, 0.0, 128.0, 128.0));
	TexA->unbind();
	TexB->unbind();
}

void prepareSettings(App::Settings *settings) {
	settings->setConsoleWindowEnabled(true);
}


CINDER_APP( MultiBindApp, RendererGl, prepareSettings)

Fragment shader:

#version 150
uniform sampler2D TextureA;
uniform sampler2D TextureB;
in vec2				TexCoord;

out vec4 	oColor;

void main( void )
{
	oColor.xyz =  texture( TextureA,TexCoord).xyz;
}	

No matter what I do, the best I get shown is the last bound texture. So if I bind TexA & then TexB, my shader will only ever show TexB (no matter if I put texture(TextureA…); etc.)

I’ve experimented with different bind methods too - using bind() & then sending TexA->getId() as the uniform index instead, which returns blank.

I’m not really sure what else to try - I was hoping a test case app would highlight where I was going wrong, but instead it’s confused me further. I’m hoping there’s a standard path to this that I’m missing.
I get similar issues in the _opengl samples too - the normal mapping adv. sample seems affected.

Any help is greatly appreciated, I’m completely stuck!


#2

Could it be that your shader is only sampling one texture? Its probably something to do with the driver trying to match the bound textures to the shader samplers, but if you dont use TextureB in the shader the driver will just remove the uniform as it isnt being used.

you could also try something like this in your draw function (Scoped state variables generally reduce complexity)

ScopedGlslProg glslScp(Multibind);
ScopedTextureBind tex0Scp(TexA, 0);
ScopedTextureBind tex0Scp(TexB, 1);
Multibind->uniform("TextureA", 0);
Multibind->uniform("TextureB", 1);

gl::drawSolidRect(Rectf(0.0, 0.0, 128.0, 128.0));

And something like this in your shader to see if both textures are bound

vec4 colA = texture( TextureA, TexCoord );
vec4 colB = texture( TextureB, TexCoord );
oColor = colA + colB;

#3

Perfect - seems to work. I did have to make sure the shader was consuming the two textures. Bizarrely, I’ve just tried my first post code on an nvidia machine where the results are fine, but I now suspect this to may just be leniency on behalf of those drivers & not the best case. Definitely learning a lesson to test across many devices right now!

I have until now stayed away a little from scoped binds for this - as I need to wrap them in a conditional in my situation -

if( thisothertex ) {
thisothertex->bind();
shader->uniform("", etc etc);
}

Where the shader always consumes the tex sample anyway. ofc with the conditional, they are always going to go out of scope.
However typing that out suggests I may be just as well to conditionally bind a dummy sample as a fallback; it makes no sense to rely on unknown behaviour. As you point out, scoping it definitely reduces complexity & works, so I will go down this path.

Perfect, thank you!


#4

AMD drivers are more strict in their support for OpenGL, so usually if it works on AMD it also works on NVIDIA but not the other way around.

Good to hear it’s working now. A few things I would have suggested is to make sure textures are enabled (in Cinder, prior to v0.9, you had to call enableAndBind() at least once). The other suggestion would have been to make sure the shader writes to the oColor.a component as well. In general, it would be smart to add a try-catch block to LoadShaders to catch any shader compilation errors.

A suggestion of a different nature would be to prefix your variable names with m for member variables, which is what most C++ programmers do nowadays, but that is a matter of preference I guess.

-Paul


#5

Great stuff, thank you for the info! Good to hear about AMD being much stricter - definitely matches up with what I’ve been finding. I’ve already patched up a few shader compile issues I was hitting. I think I’ll try and stick to AMD as a main development config.

@felixfaire Just popping back in to say thanks again - just trying this out in the wild & it’s definitely solved my issues. I’ve used scoped texture binds now, and handled the failures with a default texture.