I'm super-baffled by this. I'm trying to render to an off-screen texture so I can perform some post-processing, but can't even get that texture to draw to the screen unmodified. I'm currently targeting OpenGL ES 2.0 on the iPhone simulator.
I've narrowed down the problem to GLSL's texture2D() function returning vec4(0, 0, 0, 1) since, if I replace that call with any a constant color, the screen fills with the specified color. The texture is created, bound to texture unit 0, its storage is allocated, its min and mag filters set to NEAREST, and the sampler2D uniform is set to 0.
I've tried removing all the render-to-texture code and initializing its data explicitly and I get the same result, and if target the screen framebuffer directly, I get the image I expect, so I'm fairly confident the texture's data is all defined by the time I try to sample from it.
I've tried making sure my texture isn't bound to any texture unit while it's being rendered to, but that didn't make a difference.
I've also tried glEnable(GL_TEXTURE_2D) but I was under the impression that doesn't matter for ES 2.0 anymore. It didn't help anyway.
I'm actually using my own thin C++ wrapper library around OpenGL ES 2.0, but if you know OpenGL ES 2.0 well, what's happening in this code should be clear regardless. I apologize for the code not being plain OpenGL; my next debugging step is to re-write the code without my wrapper library. I was just wondering if I'm making any bonehead mistakes that jump out anyway.
I have, however, added plain OpenGL glGet*() queries before the last drawArrays call for all the state variables I set, and everything gets set as expected.
The only not-super-obvious part of the code are the Smart<> objects. They simply wrap GL object references and reference arrays and call their associated glDelete*() in their destructors.
#include <tsvl/vec.hpp>
using namespace tsvl;
#include <tsgl2/Context.hpp>
#include <tsgl2/Smart.hpp>
using namespace tsgl2;
#include <array>
#include <exception>
#include <string>
using namespace std;
#define SHADER_SOURCE(text) "#version 100\n" #text
namespace {
const AttributeLocation vertexPositionAttributeLocation(0);
const string vec2PassthroughVertexShaderSource = SHADER_SOURCE(
attribute vec2 vertexPosition;
void main() {
gl_Position = vec4(vertexPosition, 0, 1);
}
);
const string greenFragmentShaderSource = SHADER_SOURCE(
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
);
const string combineTexturesFragmentShaderSource = SHADER_SOURCE(
precision highp float;
uniform vec2 screenSize;
uniform sampler2D samplers[1];
void main() {
vec2 texCoord = gl_FragCoord.xy / screenSize;
gl_FragColor = texture2D(samplers[0], texCoord);
}
);
const vec2 vertices[] = {
vec2(-0.9f, -0.9f),
vec2( 0.9f, -0.9f),
vec2( 0.0f, 0.9f),
};
const int vertexCount = sizeof(vertices) / sizeof(vertices[0]);
const vec2 screenCorners[] = {
vec2(-1.0f, -1.0f),
vec2( 1.0f, -1.0f),
vec2( 1.0f, 1.0f),
vec2(-1.0f, 1.0f),
};
const int screenCornerCount = sizeof(screenCorners) / sizeof(screenCorners[0]);
} // unnamed namespace
void drawDemoScene(int screenWidth, int screenHeight) {
FramebufferRef screenFramebuffer = Framebuffer::currentBinding();
//
Smart<array<TextureRef, 8>> renderTextures(Context::genTextures<8>());
Smart<array<FramebufferRef, 8>> renderFramebuffers(Context::genFramebuffers<8>());
Context::setActiveTextureUnit(TextureUnit(0)); // My wrapper translates this to GL_TEXTURE0
Texture2D::bind(renderTextures.get()[0]);
Texture2D::setStorage(TextureFormat::RGBA8, IntRect::Size(screenWidth, screenHeight));
Texture2D::setMinificationFilter(TextureMinificationFilter::NEAREST);
Texture2D::setMagnificationFilter(TextureMagnificationFilter::NEAREST);
Framebuffer::bind(renderFramebuffers.get()[0]);
Framebuffer::ColorAttachment::set(renderTextures.get()[0], FramebufferTextureTarget::TEXTURE_2D);
if (Framebuffer::status() != FramebufferStatus::COMPLETE)
throw exception();
//
vertexPositionAttributeLocation.enableAttributeArray();
Smart<ShaderRef> vec2PassthroughVertexShader(Context::createShader(ShaderType::VERTEX));
vec2PassthroughVertexShader->setSource(vec2PassthroughVertexShaderSource);
vec2PassthroughVertexShader->compile();
if (!vec2PassthroughVertexShader->compileWasSuccessful())
throw exception();
Smart<ShaderRef> greenFragmentShader(Context::createShader(ShaderType::FRAGMENT));
greenFragmentShader->setSource(greenFragmentShaderSource);
greenFragmentShader->compile();
if (!greenFragmentShader->compileWasSuccessful())
throw exception();
Smart<ShaderRef> combineTexturesFragmentShader(Context::createShader(ShaderType::FRAGMENT));
combineTexturesFragmentShader->setSource(combineTexturesFragmentShaderSource);
combineTexturesFragmentShader->compile();
if (!combineTexturesFragmentShader->compileWasSuccessful())
throw exception();
Smart<ProgramRef> vec2PassthroughGreenProgram(Context::createProgram());
vec2PassthroughGreenProgram->attach(*vec2PassthroughVertexShader);
vec2PassthroughGreenProgram->attach(*greenFragmentShader);
vec2PassthroughGreenProgram->bindAttributeToLocation(
"vertexPosition", vertexPositionAttributeLocation);
vec2PassthroughGreenProgram->link();
vec2PassthroughGreenProgram->validate();
if (!vec2PassthroughGreenProgram->validationWasSuccessful())
throw exception();
Smart<ProgramRef> combineTexturesProgram(Context::createProgram());
combineTexturesProgram->attach(*vec2PassthroughVertexShader);
combineTexturesProgram->attach(*combineTexturesFragmentShader);
combineTexturesProgram->bindAttributeToLocation(
"vertexPosition", vertexPositionAttributeLocation);
combineTexturesProgram->link();
combineTexturesProgram->validate();
if (!combineTexturesProgram->validationWasSuccessful())
throw exception();
UniformLocation screenSizeUniformLocation =
combineTexturesProgram->locationOfUniform("screenSize");
UniformLocation samplersUniformLocation =
combineTexturesProgram->locationOfUniform("samplers");
//
Context::setColorClearValue(0.0f, 0.0f, 0.0f, 0.0f);
Context::setLineWidth(2.0f);
Context::setViewport(IntRect(0, 0, screenWidth, screenHeight));
Context::useProgram(*vec2PassthroughGreenProgram);
Framebuffer::bind(renderFramebuffers.get()[0]);
vertexPositionAttributeLocation.setAttributeArrayPointerAndStride(
DONT_NORMALIZE, 2, AttributeLocation::ArrayDataType::FLOAT, vertices, 0);
Context::clear(CLEAR_COLOR_BUFFER);
Context::drawArrays(DrawMode::LINE_LOOP, 0, vertexCount);
//
Context::enableBlending();
Context::setBlendFuncs(SourceBlendFunc::SRC_ALPHA, DestBlendFunc::ONE_MINUS_SRC_ALPHA);
Context::setColorClearValue(1.0f, 1.0f, 1.0f, 1.0f);
Context::setViewport(IntRect(0, 0, screenWidth, screenHeight));
Context::useProgram(*combineTexturesProgram);
Framebuffer::bind(screenFramebuffer);
vertexPositionAttributeLocation.setAttributeArrayPointerAndStride(
DONT_NORMALIZE, 2, AttributeLocation::ArrayDataType::FLOAT, screenCorners, 0);
screenSizeUniformLocation.setUniformValue(vec2(screenWidth, screenHeight));
samplersUniformLocation.setUniformValue(TextureUnit(0)); // Even though setActiveTextureUnit()
// translated this to GL_TEXTURE0
// It stays plain int 0 here.
Context::clear(CLEAR_COLOR_BUFFER);
Context::drawArrays(DrawMode::TRIANGLE_FAN, 0, screenCornerCount);
}
You can see how my wrapper library is implemented here: https://github.com/jbat-es/tsgl2
EDIT:
Okay, I wrote a very stripped down version in plain OpenGL and it has the same problem:
void drawDemoScenePlainGL(int screenWidth, int screenHeight) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
int data[320][460];
memset(data, 0xFF, 320*460*sizeof(int));
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenWidth, screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
//
glEnableVertexAttribArray(0);
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
const char* source = vec2PassthroughVertexShaderSource.c_str();
int sourceLength = vec2PassthroughVertexShaderSource.length();
glShaderSource(vertexShader, 1, &source, &sourceLength);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
source = combineTexturesFragmentShaderSource.c_str();
sourceLength = combineTexturesFragmentShaderSource.length();
glShaderSource(fragmentShader, 1, &source, &sourceLength);
glCompileShader(fragmentShader);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glBindAttribLocation(program, 0, "vertexPosition");
glLinkProgram(program);
GLuint screenSizeUniformLocation = glGetUniformLocation(program, "screenSize");
GLuint samplersUniformLocation = glGetUniformLocation(program, "samplers");
//
glClearColor(1, 1, 1, 1);
glUseProgram(program);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, screenCorners);
glViewport(0, 0, screenWidth, screenHeight);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform2f(screenSizeUniformLocation, screenWidth, screenHeight);
glUniform1i(samplersUniformLocation, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_FAN, 0, screenCornerCount);
}
The texture wrap modes default to GL_REPEAT which only support power-of-two textures. Simply adding
Solves the problem.