I'm trying to get Vuforia 6.0.117 working in my Android app. I'm using this specific version since its the last version supporting FrameMarkers. The detection of FrameMarkers is working fine, but when i'm trying to render a texture over the FrameMarker on my phone I get an error stating:
After operation FrameMarkers render frame got glError 0x501
My renderFrame method:
// Clear color and depth buffer
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Get the state from Vuforia and mark the beginning of a rendering
// section
State state = Renderer.getInstance().begin();
// Explicitly render the Video Background
Renderer.getInstance().drawVideoBackground();
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendEquation(GLES20.GL_FUNC_ADD);
// GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// We must detect if background reflection is active and adjust the
// culling direction.
// If the reflection is active, this means the post matrix has been
// reflected as well,
// therefore standard counter clockwise face culling will result in
// "inside out" models.
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_BACK);
if (Renderer.getInstance().getVideoBackgroundConfig().getReflection() == VIDEO_BACKGROUND_REFLECTION.VIDEO_BACKGROUND_REFLECTION_ON) {
GLES20.glFrontFace(GLES20.GL_CW); // Front camera
} else {
GLES20.glFrontFace(GLES20.GL_CCW); // Back camera
}
// Did we find any trackables this frame?
if (mActivity.isHelpVisible() || state.getNumTrackableResults() == 0) {
// no marker scanned
mActivity.hideInfoButton();
} else {
// Get the trackable:
TrackableResult trackableResult = state.getTrackableResult(0);
float[] modelViewMatrix = Tool.convertPose2GLMatrix(trackableResult.getPose()).getData();
// Check the type of the trackable:
MarkerResult markerResult = (MarkerResult) trackableResult;
Marker marker = (Marker) markerResult.getTrackable();
if (markerId != marker.getMarkerId()) {
markerId = marker.getMarkerId();
tag = DataManager.getInstance().getTagByMarkerId(markerId);
if (tag != null) {
texture = Texture.loadTexture(tag.getTexture());
setupTexture(texture);
tag.addToDB();
}
}
if (tag != null) {
String poiReference = tag.getPoiReference();
if (!poiReference.isEmpty()) {
mActivity.showInfoButton(poiReference);
}
// Select which model to draw:
Buffer vertices = planeObject.getVertices();
Buffer normals = planeObject.getNormals();
Buffer indices = planeObject.getIndices();
Buffer texCoords = planeObject.getTexCoords();
int numIndices = planeObject.getNumObjectIndex();
float[] modelViewProjection = new float[16];
float scale = (float) tag.getScale();
Matrix.scaleM(modelViewMatrix, 0, scale, scale, scale);
Matrix.multiplyMM(modelViewProjection, 0, vuforiaAppSession.getProjectionMatrix().getData(), 0, modelViewMatrix, 0);
GLES20.glUseProgram(shaderProgramID);
GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT, false, 0, vertices);
GLES20.glVertexAttribPointer(normalHandle, 3, GLES20.GL_FLOAT, false, 0, normals);
GLES20.glVertexAttribPointer(textureCoordHandle, 2, GLES20.GL_FLOAT, false, 0, texCoords);
GLES20.glEnableVertexAttribArray(vertexHandle);
GLES20.glEnableVertexAttribArray(normalHandle);
GLES20.glEnableVertexAttribArray(textureCoordHandle);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture.mTextureID[0]);
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, modelViewProjection, 0);
GLES20.glUniform1i(texSampler2DHandle, 0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, numIndices, GLES20.GL_UNSIGNED_SHORT, indices);
GLES20.glDisableVertexAttribArray(vertexHandle);
GLES20.glDisableVertexAttribArray(normalHandle);
GLES20.glDisableVertexAttribArray(textureCoordHandle);
SampleUtils.checkGLError("FrameMarkers render frame");
}
}
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
Renderer.getInstance().end();
}
I'm loading a texture of the size 640x482 and is loading as follows:
public class Texture {
public int mWidth; // The width of the texture.
public int mHeight; // The height of the texture.
public int mChannels; // The number of channels.
public ByteBuffer mData; // The pixel data.
public int[] mTextureID = new int[1];
public boolean mSuccess = false;
public static Texture loadTexture(String fileName) {
try {
InputStream inputStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(inputStream);
Bitmap bitMap = BitmapFactory.decodeStream(bufferedStream);
bufferedStream.close();
inputStream.close();
int[] data = new int[bitMap.getWidth() * bitMap.getHeight()];
bitMap.getPixels(data, 0, bitMap.getWidth(), 0, 0, bitMap.getWidth(), bitMap.getHeight());
return loadTextureFromIntBuffer(data, bitMap.getWidth(), bitMap.getHeight());
} catch (IOException e) {
Log.e(Constants.DEBUG, "Failed to load texture '" + fileName + "' from APK");
Log.i(Constants.DEBUG, e.getMessage());
return null;
}
}
public static Texture loadTextureFromIntBuffer(int[] data, int width, int height) {
// Convert:
int numPixels = width * height;
byte[] dataBytes = new byte[numPixels * 4];
for (int p = 0; p < numPixels; ++p) {
int colour = data[p];
dataBytes[p * 4] = (byte) (colour >>> 16); // R
dataBytes[p * 4 + 1] = (byte) (colour >>> 8); // G
dataBytes[p * 4 + 2] = (byte) colour; // B
dataBytes[p * 4 + 3] = (byte) (colour >>> 24); // A
}
Texture texture = new Texture();
texture.mWidth = width;
texture.mHeight = height;
texture.mChannels = 4;
texture.mData = ByteBuffer.allocateDirect(dataBytes.length).order(ByteOrder.nativeOrder());
int rowSize = texture.mWidth * texture.mChannels;
for (int r = 0; r < texture.mHeight; r++) {
texture.mData.put(dataBytes, rowSize * (texture.mHeight - 1 - r), rowSize);
}
texture.mData.rewind();
texture.mSuccess = true;
return texture;
}
}
Anybody got an idea why i'm getting this error and how to fix it?
I cannot go over your entire code right now, and even if I could I'm not sure it would help. You first need to narrow down the problem, so I will first give you the method to do that, and I hope it will serve you in other cases as well.
You managed to find out that there was an error - but you are checking it only at the end of the rendering function. What you need to do is to place the checkGLError call in several places inside the rendering code (print a different text message), until you can pin-point the exact line after which the error first appears. Then, if you cannot understand the problem, comment here what is the problematic line and I will try to help.
UPDATE:
After looking at the shader code, following your report that normalHandle is -1, I got to the following conclusions:
The error, which indicates the variable vertexNormal cannot be found in the shader, may be due to the fact that this variable is probably optimized out during shader compilation, since it is not really required.
Explanation: in the vertex shader (CUBE_MESH_VERTEX_SHADER), vertexNormal is assigned to a varying called normal (variable that is passed to the fragment shader). In the fragment shader, this varying is declared but not used.
Therefore, you can actually delete the variables vertexNormal and normal from the shader, and you can delete all usages of 'normalHandle' in your code.
This should eliminate the error.
Related
I'm need to send data from GL_TEXTURE_EXTERNAL_OES to simple GL_TEXTURE_2D (Render image from Android player to Unity texture) and currently do it through read pixels from buffer with attached source texture. This process work correctly on my OnePlus 5 phone, but have some glitches with image on phones like xiaomi note 4, mi a2 and etc (like image is very green), and also there is perfomance issues becouse of this process works every frame and than more pixels to read, than worser perfomance (even my phone has low fps at 4k resolution). Any idea how to optimize this process or do it in some other way?
Thanks and best regards!
GLuint FramebufferName;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, g_ExtTexturePointer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
LOGD("%s", "Error: Could not setup frame buffer.");
}
unsigned char* data = new unsigned char[g_SourceWidth * g_SourceHeight * 4];
glReadPixels(0, 0, g_SourceWidth, g_SourceHeight, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, g_TexturePointer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_SourceWidth, g_SourceHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glDeleteFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
delete[] data;
UPDATE.
Function which contain this code and function which calls it from Unity side
static void UNITY_INTERFACE_API OnRenderEvent(int eventID) { ... }
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UMDGetRenderEventFunc()
{
return OnRenderEvent;
}
Which called from Unity Update function like this:
[DllImport("RenderingPlugin")]
static extern IntPtr UMDGetRenderEventFunc();
IEnumerator UpdateVideoTexture()
{
while (true)
{
...
androidPlugin.UpdateSurfaceTexture();
GL.IssuePluginEvent(UMDGetRenderEventFunc, 1);
}
}
And Android plugin do this on its side (surfaceTexture its texture which contain this external texture on which ExoPlayer render video)
public void exportUpdateSurfaceTexture() {
synchronized (this) {
if (this.mIsStopped) {
return;
}
surfaceTexture.updateTexImage();
}
}
On the C++ side:
You're creating and destroying pixel data every frame when you do new unsigned char[g_SourceWidth * g_SourceHeight * 4]; and delete[] data and that's expensive depending on the Texture size. Create the texture data once then re-use it.
One way to do this is to have static variables on the C++ side hold the texture information then a function to initialize those variables::
static void* pixelData = nullptr;
static int _x;
static int _y;
static int _width;
static int _height;
void initPixelData(void* buffer, int x, int y, int width, int height) {
pixelData = buffer;
_x = x;
_y = y;
_width = width;
_height = height;
}
Then your capture function should be re-written to remove new unsigned char[g_SourceWidth * g_SourceHeight * 4]; and delete[] data but use the static variables.
static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
if (pixelData == nullptr) {
//Debug::Log("Pointer is null", Color::Red);
return;
}
GLuint FramebufferName;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, g_ExtTexturePointer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
LOGD("%s", "Error: Could not setup frame buffer.");
}
glReadPixels(_x, _y, _width, _height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
glBindTexture(GL_TEXTURE_2D, g_TexturePointer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
glDeleteFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UMDGetRenderEventFunc()
{
return OnRenderEvent;
}
On the C# side:
[DllImport("RenderingPlugin", CallingConvention = CallingConvention.Cdecl)]
public static extern void initPixelData(IntPtr buffer, int x, int y, int width, int height);
[DllImport("RenderingPlugin", CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr UMDGetRenderEventFunc();
Create the Texture information, pin it and send the pointer to C++:
int width = 500;
int height = 500;
//Where Pixel data will be saved
byte[] screenData;
//Where handle that pins the Pixel data will stay
GCHandle pinHandler;
//Used to test the color
public RawImage rawImageColor;
private Texture2D texture;
// Use this for initialization
void Awake()
{
Resolution res = Screen.currentResolution;
width = res.width;
height = res.height;
//Allocate array to be used
screenData = new byte[width * height * 4];
texture = new Texture2D(width, height, TextureFormat.RGBA32, false, false);
//Pin the Array so that it doesn't move around
pinHandler = GCHandle.Alloc(screenData, GCHandleType.Pinned);
//Register the screenshot and pass the array that will receive the pixels
IntPtr arrayPtr = pinHandler.AddrOfPinnedObject();
initPixelData(arrayPtr, 0, 0, width, height);
StartCoroutine(UpdateVideoTexture());
}
Then to update the texture, see the sample below. Note that there are two methods to update the texture as shown on the code below. If you run into issues with Method1, comment out the two lines which uses texture.LoadRawTextureData and texture.Apply and un-comment the Method2 code which uses the ByteArrayToColor, texture.SetPixels and texture.Apply function:
IEnumerator UpdateVideoTexture()
{
while (true)
{
//Take screenshot of the screen
GL.IssuePluginEvent(UMDGetRenderEventFunc(), 1);
//Update Texture Method1
texture.LoadRawTextureData(screenData);
texture.Apply();
//Update Texture Method2. Use this if the Method1 above crashes
/*
ByteArrayToColor();
texture.SetPixels(colors);
texture.Apply();
*/
//Test it by assigning the texture to a raw image
rawImageColor.texture = texture;
//Wait for a frame
yield return null;
}
}
Color[] colors = null;
void ByteArrayToColor()
{
if (colors == null)
{
colors = new Color[screenData.Length / 4];
}
for (int i = 0; i < screenData.Length; i += 4)
{
colors[i / 4] = new Color(screenData[i],
screenData[i + 1],
screenData[i + 2],
screenData[i + 3]);
}
}
Unpin the array when done or when the script is about to be destroyed:
void OnDisable()
{
//Unpin the array when disabled
pinHandler.Free();
}
Calling glReadPixels is always going to be slow; CPUs are not good at bulk data transfer.
Ideally you'd managed to convince Unity to accept an external image handle, and do the whole process zero copy, but failing that I would use a GPU render-to-texture and use a shader to transfer from the external image to the RGB surface.
I started developing a small cross platform game engine using OpenGL, I am working actually on a sprite batch renderer.
Everything is working fine on OS X, iOS, Win32, and some Android Devices. Here is the Results :
The image on the left show the correct result, and was tested on Samsung galaxy S1, Galaxy J5, Galaxy S3, Galaxy Tab 3.
The device that give these weird results on the right is a Samsung Galaxy Core 2, With a Mali-400 MP renderer.
I start getting that weird result when I use more than one texture.
Here is my Sprite Batch class :
Glyph::Glyph(const maths::vec2 &position, const maths::vec2 &dimensions, const maths::vec4 &uvRect, GLuint texture, unsigned int color, float zOrder) :
textureID(texture) {
a_zOrder = zOrder;
topLeft.m_color = color;
topLeft.setPosition(position.x, position.y + dimensions.y);
topLeft.setUV(uvRect.x, uvRect.y + uvRect.w);
bottomLeft.m_color = color;
bottomLeft.setPosition(position.x, position.y);
bottomLeft.setUV(uvRect.x, uvRect.y);
bottomRight.m_color = color;
bottomRight.setPosition(position.x + dimensions.x, position.y);
bottomRight.setUV(uvRect.x + uvRect.z, uvRect.y);
topRight.m_color = color;
topRight.setPosition(position.x + dimensions.x, position.y + dimensions.y);
topRight.setUV(uvRect.x + uvRect.z, uvRect.y + uvRect.w);
}
//SpriteBatch
ORendererSpriteBatch::ORendererSpriteBatch(): m_vboID(0), m_vaoID(0)
{
Init();
}
ORendererSpriteBatch::~ORendererSpriteBatch()
{
if (m_vboID != 0) {
glDeleteBuffers(1, &m_vboID);
}
if (m_vaoID != 0) {
glDeleteVertexArrays(1, &m_vaoID);
}
}
void ORendererSpriteBatch::Init()
{
createVertexArray();
}
void ORendererSpriteBatch::Begin()
{
m_renderBatches.clear();
// Makes _glpyhs.size() == 0, however it does not free internal memory.
// So when we later call emplace_back it doesn't need to internally call new.
m_glyphs.clear();
}
void ORendererSpriteBatch::Submit(const OSprite* renderable)
{
m_glyphs.emplace_back(renderable->GetPosition(), renderable->GetSize(), renderable->GetUV(), renderable->GetTID(), renderable->GetColor(), renderable->GetZOrder());
}
void ORendererSpriteBatch::End()
{
// Set up all pointers for fast sorting
m_glyphPointers.resize(m_glyphs.size());
for (size_t i = 0; i < m_glyphs.size(); i++) {
m_glyphPointers[i] = &m_glyphs[i];
}
sortGlyphs();
createRenderBatches();
}
void ORendererSpriteBatch::Flush(OLayer2D *layer)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
ORendererFactory::OShader_Simple2D->bind();
glActiveTexture(GL_TEXTURE0);
ORendererFactory::OShader_Simple2D->setUniform1i("u_diffuse", 0);
ORendererFactory::OShader_Simple2D->setUniformMat4("u_MVP", layer->getCamera()->getCameraMatrix());
glBindVertexArray(m_vaoID);
for (size_t i = 0; i < m_renderBatches.size(); i++) {
glBindTexture(GL_TEXTURE_2D, m_renderBatches[i].texture);
glDrawArrays(GL_TRIANGLES, m_renderBatches[i].offset, m_renderBatches[i].numVertices);
}
ORendererFactory::OShader_Simple2D->unbind();
glBindVertexArray(0);
}
void ORendererSpriteBatch::createRenderBatches() {
// This will store all the vertices that we need to upload
std::vector <VertexData2D> vertices;
// Resize the buffer to the exact size we need so we can treat
// it like an array
vertices.resize(m_glyphPointers.size() * 6);
if (m_glyphPointers.empty()) {
return;
}
int offset = 0; // current offset
int cv = 0; // current vertex
//Add the first batch
m_renderBatches.emplace_back(offset, 6, m_glyphPointers[0]->textureID);
vertices[cv++] = m_glyphPointers[0]->topLeft;
vertices[cv++] = m_glyphPointers[0]->bottomLeft;
vertices[cv++] = m_glyphPointers[0]->bottomRight;
vertices[cv++] = m_glyphPointers[0]->bottomRight;
vertices[cv++] = m_glyphPointers[0]->topRight;
vertices[cv++] = m_glyphPointers[0]->topLeft;
offset += 6;
//Add all the rest of the glyphs
for (size_t cg = 1; cg < m_glyphPointers.size(); cg++) {
// Check if this glyph can be part of the current batch
if (m_glyphPointers[cg]->textureID != m_glyphPointers[cg - 1]->textureID) {
// Make a new batch
m_renderBatches.emplace_back(offset, 6, m_glyphPointers[cg]->textureID);
} else {
// If its part of the current batch, just increase numVertices
m_renderBatches.back().numVertices += 6;
}
vertices[cv++] = m_glyphPointers[cg]->topLeft;
vertices[cv++] = m_glyphPointers[cg]->bottomLeft;
vertices[cv++] = m_glyphPointers[cg]->bottomRight;
vertices[cv++] = m_glyphPointers[cg]->bottomRight;
vertices[cv++] = m_glyphPointers[cg]->topRight;
vertices[cv++] = m_glyphPointers[cg]->topLeft;
offset += 6;
}
glBindVertexArray(m_vaoID);
// Bind our VBO
glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
// Orphan the buffer (for speed)
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(VertexData2D), NULL, GL_DYNAMIC_DRAW);
// Upload the data
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(VertexData2D), vertices.data());
glBindVertexArray(0);
// Unbind the VBO
// glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void ORendererSpriteBatch::createVertexArray() {
// Generate the VAO if it isn't already generated
if (m_vaoID == 0) {
glGenVertexArrays(1, &m_vaoID);
}
// Bind the VAO. All subsequent opengl calls will modify it's state.
glBindVertexArray(m_vaoID);
//G enerate the VBO if it isn't already generated
if (m_vboID == 0) {
glGenBuffers(1, &m_vboID);
}
glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
//Tell opengl what attribute arrays we need
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_vertex));
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE , sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_color));
glVertexAttribPointer(2, 2, GL_FLOAT , GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_uv));
glBindVertexArray(0);
}
void ORendererSpriteBatch::sortGlyphs() {
std::stable_sort(m_glyphPointers.begin(), m_glyphPointers.end(), compareFunction);
}
bool ORendererSpriteBatch::compareFunction(Glyph* a, Glyph* b) {
if (a->a_zOrder == b->a_zOrder) {
return (a->textureID < b->textureID);
}
return (a->a_zOrder < b->a_zOrder);
}
And here is how I call that class :
m_CurrentRenderer->Begin();
for (const OSprite* renderable : m_Renderables)
if(m_Camera->isBoxInView(renderable->GetPosition(), renderable->GetSize())){
renderable->Submit(m_CurrentRenderer);
}
m_CurrentRenderer->End();
m_CurrentRenderer->Flush(this);
Any suggestion about what might cause that bug is welcome.
Your code:
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_vertex));
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE , sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_color));
glVertexAttribPointer(2, 2, GL_FLOAT , GL_FALSE, sizeof(VertexData2D), (void *)offsetof(VertexData2D, m_uv));
Assumes that the vertex position is attribute #0, colour is attribute #1 and uv is attribute #2.
Do you enforce that in any way? Declaring the attributes in that order in your vertex shader doesn't guarantee that the attributes will take on that order.
You can specify the attribute locations using glBindAttribLocation prior to linking the program, or you can use glGetAttribLocation to query the location instead.
I have a world full of 2D squares (z=0). At startup I setup projection in such a way that the whole world is visible on the screen, using:
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrthof(left, right, bottom, top, zNear, zFar);
Then I allow the user to zoom in the world with fingers by using:
gl.glScalef(mScaleFactor, mScaleFactor, 1.0f);
I want to make visibility test for objects that appear not visible as the user is zooming in to not render them (performance boost).
I found this method:
android.opengl.Visibility.visibilityTest(float[] ws, int wsOffset, float[] positions, int positionsOffset, char[] indices, int indicesOffset, int indexCount);
But I can't make it work, nor I found ANY examples of the usage of this method on Internet. Currently this method returns result=0 for every square I test, even when scaling is not applied (mScaleFactor = 1.0)
The way I'm doing this:
final short SQUARE_VERTICES_ORDER_TEMPLATE[] = {0, 1, 2, 0, 2, 3};
.....
float[] vertices = toArray(mVertexBuffer);
short[] indices = toArray(mIndicesBuffer);
char[] charIndices = new char[indices.length];
// method needs char[]
for (int i = 0; i < indices.length; i++) {
short shortIndex = indices[i];
charIndices[i] = (char) shortIndex;
}
for (int i = 0; i < mSquares.size(); i++) {
int numIndicesPerSquare = SQUARE_VERTICES_ORDER_TEMPLATE.length;
int indicesOffset = i * numIndicesPerSquare;
int result = Visibility.visibilityTest(matrixGrabber.mProjection, 0, vertices, 0, charIndices, indicesOffset, numIndicesPerSquare);
switch (result) {
case 0:
Log.v(TAG, "Object NOT VISIBLE: " + mSquares.get(i)); // hits every time
break;
case 1:
Log.v(TAG, "Object PARTIALLY VISIBLE: " + mSquares.get(i));
break;
default:
TAG.toString(); // to place a break point
break;
}
}
I'm not sure if I'm picking up the right Matrix required by this method.
Could you please validate the right usage of this method or give any other tips or workarounds ?
I've figured out what is wrong, visibilityTest method requires multiplied matrix. Here is how it should be:
matrixGrabber.getCurrentState(gl);
float[] resultMatrix = new float[matrixGrabber.mProjection.length];
Matrix.multiplyMM(resultMatrix, 0, matrixGrabber.mProjection, 0, matrixGrabber.mModelView, 0);
....
Visibility.visibilityTest(resultMatrix, 0, vertices, 0, charIndices, indicesOffset, numIndicesPerSquare);
I attempting to render an exported model in .obj form (exported from blender). It is a sphere and my app crashes only on Nexus 5 phone. It is working on Andy emulator and other Android devices.
While debugging, I tried changing the value of the 'count' parameter, to see what would happen. My sphere has 960 faces (so I should draw 2880 to see the full model). However, if I put 1785 or more, it will crash. Using 1784 or less, it doesn't crash (but I only see a part of the model).
It crash on GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 1784);
private void draw()
{
mCubePositions.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mCubePositions);
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Pass in the normal information
mCubeNormals.position(0);
GLES20.glVertexAttribPointer(mNormalHandle, 3, GLES20.GL_FLOAT, false, 0, mCubeNormals);
GLES20.glEnableVertexAttribArray(mNormalHandle);
// This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
// (which currently contains model * view).
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
// Pass in the modelview matrix.
GLES20.glUniformMatrix4fv(mMVMatrixHandle, 1, false, mMVPMatrix, 0);
// This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
// (which now contains model * view * projection).
Matrix.multiplyMM(mTemporaryMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
System.arraycopy(mTemporaryMatrix, 0, mMVPMatrix, 0, 16);
// Pass in the combined matrix.
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// Pass in the light position in eye space.
GLES20.glUniform3f(mLightPosHandle, mLightPosInEyeSpace[0], mLightPosInEyeSpace[1], mLightPosInEyeSpace[2]);
// Draw the cube.
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 1784);
}
My OBJ reader
public static void readModel(Context context, int resId) {
// read in all the lines and put in their respective arraylists of strings
// reason I do this is to get a count of the faces to be used to initialize the
// float arrays
ArrayList<String> vertexes = new ArrayList<String>();
ArrayList<String> vertexNormals = new ArrayList<String>();
ArrayList<String> textures = new ArrayList<String>();
ArrayList<String> faces = new ArrayList<String>();
InputStream iStream = context.getResources().openRawResource(resId);
InputStreamReader isr = new InputStreamReader(iStream);
BufferedReader bReader = new BufferedReader(isr);
String line;
try {
while ((line = bReader.readLine()) != null) {
// do not read in the leading v, vt or f
if (line.startsWith("v ")) vertexes.add(line.substring(2));
if (line.startsWith("vn ")) vertexNormals.add(line.substring(3));
if (line.startsWith("vt ")) textures.add(line.substring(3));
if (line.startsWith("f ")) faces.add(line.substring(2));
}
} catch (IOException e) {
e.printStackTrace();
}
// holding arrays for the vertices, texture coords and indexes
float[] vCoords = new float[faces.size() *3*3];
float[] vNCoords = new float[faces.size()*3*3];
float[] vtCoords = new float[faces.size()*3*2];
totalCaras = faces.size();
int vertexIndex = 0;
int faceIndex = 0;
int textureIndex = 0;
// for each face
for (String i : faces) {
// for each face component
for (String j : i.split(" ")) {
String[] faceComponent = j.split("//");
String vertex = vertexes.get(Integer.parseInt(faceComponent[0]) - 1);
String normal = vertexNormals.get(Integer.parseInt(faceComponent[1]) - 1);
String vertexComp[] = vertex.split(" ");
String normalComp[] = normal.split(" ");
for (String v : vertexComp) {
vCoords[vertexIndex++] = Float.parseFloat(v);
}
for (String n : normalComp) {
vNCoords[faceIndex++] = Float.parseFloat(n);
}
}
}
mCubePositions = ByteBuffer.allocateDirect(vCoords.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubePositions.put(vCoords).position(0);
mCubeNormals = ByteBuffer.allocateDirect(vNCoords.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubeNormals.put(vNCoords).position(0);
}
The only trace that logcat shows:
06-10 09:08:32.016: D/OpenGLRenderer(6041): Use EGL_SWAP_BEHAVIOR_PRESERVED: true
06-10 09:08:32.027: D/Atlas(6041): Validating map...
06-10 09:08:32.074: I/Adreno-EGL(6041): <qeglDrvAPI_eglInitialize:379>: QUALCOMM Build: 01/14/15, ab0075f, Id3510ff6dc
06-10 09:08:32.075: I/OpenGLRenderer(6041): Initialized EGL, version 1.4
06-10 09:05:05.724: D/OpenGLRenderer(5242): Enabling debug mode 0
06-10 09:05:05.878: A/libc(5242): Fatal signal 11 (SIGSEGV), code 2, fault addr 0x74615000 in tid 5279 (GLThread 13643)
I have implemented a color picking method and it also works; sometimes. The problem is when I call my method in the onSurfaceChanged method, it does read back the correct pixels. But when I call my method in the onTouchevent of the GLSurfaceView, it reads back only zeros. In my method I create a framebuffer object and attach two renderbuffer objects to it. My renderer is set to RENDERMODE_WHEN_DIRTY and glGetError() returns 0. Where exactly is the problem? I'm not sure whether it is my method, or if I simply cannot do this in an onTouchevent. Here is the sourcecode of my picking method in my renderer:
public int pick(int x, int y) {
int result = -2;
int[] view = new int[4];
GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, view, 0);
y = view[3] - y;
int[] fbo = new int[1];
GLES20.glGenFramebuffers(1, fbo, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo[0]);
int[] rbo = new int[2];
GLES20.glGenRenderbuffers(2, rbo, 0);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, rbo[0]);
GLES20.glRenderbufferStorage(
GLES20.GL_RENDERBUFFER,
GLES20.GL_RGBA4, view[2], view[3]);
GLES20.glFramebufferRenderbuffer(
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_RENDERBUFFER, rbo[0]);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, rbo[1]);
GLES20.glRenderbufferStorage(
GLES20.GL_RENDERBUFFER,
GLES20.GL_DEPTH_COMPONENT16,
view[2], view[3]);
GLES20.glFramebufferRenderbuffer(
GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, rbo[1]);
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) {
result = -1;
GLES20.glClear(
GLES20.GL_COLOR_BUFFER_BIT |
GLES20.GL_DEPTH_BUFFER_BIT);
for (int i = 0; i < mObjects.size(); i++)
mObjects.get(i).render(mProgram, mMatrixMVP, true);
ByteBuffer pixels = ByteBuffer.allocate(view[2] * view[3] * 4);
pixels.order(ByteOrder.nativeOrder());
GLES20.glReadPixels( // I read every pixel just for debugging
0, 0, view[2], view[3], GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, pixels);
int e = GLES20.glGetError(); // always returns 0
byte[] tmp = pixels.array(); // only zeros when called in onTouch
for (int i = 0; i < mObjects.size(); i++)
if ((Math.abs(r - mObjects.get(i).CODE_R) < 8) &&
(Math.abs(g - mObjects.get(i).CODE_G) < 8) &&
(Math.abs(b - mObjects.get(i).CODE_B) < 8)) {
result = i;
break;
}
}
GLES20.glDeleteRenderbuffers(2, rbo, 0);
GLES20.glDeleteFramebuffers(1, fbo, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
return result;
}
And since it might be relevent, here is my onDrawFrame method:
#Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(
GLES20.GL_COLOR_BUFFER_BIT |
GLES20.GL_DEPTH_BUFFER_BIT);
for (int i = 0; i < mObjects.size(); i++)
mObjects.get(i).render(mProgram, mMatrixMVP, false);
}
I have read that the onDrawFrame Method runs in a seperate thread, but I don't think that this is the problem, since I am in RENDERMODE_WHEN_DIRTY...
Why dont you check this library. I think it is quite good, you could use it instead of create a new color picker.
https://github.com/LarsWerkman/HoloColorPicker
Even if you use RENDERMODE_WHEN_DIRTY your renderer runs on a different thread, it just doesn't render untill you call requestRender(). You can try to use the queueEvent()-method to run the code on your render thread instead and see if that helps.
You could also change your pick()-method so that it internally uses a Runnable and the queueEvent-method (or a Handler). You would of course also need to use some kind of callback to be able to "return" a result.