Creating OpenCL memory object from OpenGL ES mipmaps - android

My android app passes in an OpenGL texture2D to my OpenCL kernel, however when I use clCreateFromGLTexture2D() with mipmap level 1, I get CL_INVALID_MIPLEVEL.
I am creating my OpenGL texture and generating mipmaps like this:
GLES20.glGenTextures ( 2, targetTex, 0 );
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, targetTex[0]);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, image_width, image_height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 1, GLES20.GL_RGBA, image_width, image_height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
The texture is then rendered to by binding it with a FBO:
targetFramebuffer = IntBuffer.allocate(1);
GLES20.glGenFramebuffers(1, targetFramebuffer);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, targetFramebuffer.get(0));
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, targetTex[0], 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
I then create a cl memory object like so:
mem_images[0] = clCreateFromGLTexture2D(m_clContext, CL_MEM_READ_ONLY, GL_TEXTURE_2D, 1, in_tex, &err);
The memory object is created successfully when the miplevel is set to 0 and all the kernels work fine. It's only when the miplevel is set to that the above function call gives me CL_INVALID_MIPLEVEL error.
OpenCL specification docs (http://www.khronos.org/registry/cl/sdk/1.1/docs/man/xhtml/clCreateFromGLTexture2D.html) say that a possible reason could be that the OpenGL implementation does not support creating from non-zero mipmap levels. However I don't know if this is definitely the case and am not sure how to find out.
EDIT:
After Andon's reply I changed my OpenGL texture generation to following:
GLES20.glGenTextures ( 2, targetTex, 0 );
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, targetTex[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, image_width, image_height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
and after drawing into the FBO which is bound with the texture, this is how I generate mipmaps:
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, targetTex[0]);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glFinish();
I still get the same error when trying to create a CL memory object from GL texture with mipmap level not equal to 0.

There is something very unusual about the dimensions of your second-level mipmap in this code. The dimensions of mipmap LOD N+1 should be LOD N{w,h}/2. You can have LODs with the same resolution in GL, but those LODs will not be mipmaps. To make your 2D texture mipmap complete, you need log2 (max (width,height))-1 LODs in addition to the base image 0, and each should be 1/4 the resolution (1/2 each dimension) of the last. I have to agree with CL on this one, LOD 1 is not really a mipmap.
Moreover, you are calling glGenerateMipmap (...) on a newly created texture that has no data store. That is a meaningless thing to do, glGenerateMipmap (...) will build your mip-chain for you starting with the base LOD image; at the time you called this, there was no base LOD.
Your call to GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); really ought to be moved to a point after you draw into your FBO. Done properly, you do not have to manually allocate storage for lower detail mipmap LODs. This function will take care of that for you. Thus, you should actually remove the code that allocates LOD 1 in the first place.

Related

Android OpenGL - glTexImage2D causing crash using ByteBuffer wrapped over byte[] with offset

Problem: Passing a ByteBuffer wrapping a byte[] containing image data into GLES20.glTexImage2D(...) is working for me if and only if the ByteBuffer has no offset - otherwise (if there's an offset set) it crashes unhelpfully with something like:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x61616162636575 in tid 32016 (GLThread 4912), pid 31806
Background: I have a single byte[] of YUV (I420) data for a 720p image - and I need to read this out as 3 separate ByteBuffers to pass to an OpenGL shader which has 3 different sampler2D channels (1 channel per buffer).
Ideally, since all the information is there in memory, I would create three ByteBuffers wrapping different parts of the byte[] like this:
ByteBuffer yBuffer = ByteBuffer.wrap(rawYUVData, 0, yBufferSize);
ByteBuffer uBuffer = ByteBuffer.wrap(rawYUVData, yBufferSize, uBufferSize);
ByteBuffer vBuffer = ByteBuffer.wrap(rawYUVData, yBufferSize+uBufferSize, uBufferSize);
And then in my renderer code, I call OpenGL glTexImage2D three times also:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yTexDataHandle);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, imgWidth, imgHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yBuffer);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, uTexDataHandle);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, uWidth, uHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, uBuffer);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, vTexDataHandle);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, uWidth, uHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, vBuffer);
This is crashing. I can fix the crash just fine by copying the contents of the above uBuffer and vBuffer into new byte[]s, then rewrapping the ByteBuffers over the new arrays.
But I'm copying data needlessly, and 30x per second.
Can I not wrap ByteBuffers over the contiguous byte[], with offsets, and use these in OpenGL for some reason? Do I really have to copy all the time?
I've checked it's not an issue in my shader file, by removing the calls to texture2D() that involve the offending samplers.
Even just adding an offset of 1 to my construction of yBuffer, and it crashes where with 0 it works fine. (rawYUVData.length is always > yBufferSize + 1)

compressing already compressed image in android open gl

I want to compress already compressed images and pass the final double compressed data to glCompressedTexImage2D. For this I have followed below steps:
glGenTextures(1, textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, level, internalformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,GL_TEXTURE_COMPRESSED_IMAGE_SIZE,
&compressedSize );
if (compressedSize > 0){
/* call glCompressedTexImage2D to render this double compressed image.*/
}
But I am getting only 0 in compressedSize. Seems the data is not getting compressed.
GL_TEXTURE_COMPRESSED_IMAGE_SIZE isn't part of the OpenGLES 2 spec (or even OpenGLES 3.0 or 3.1)

Black artifacts with open gl es 2.0 texture rendering on Certain devices

While rendering a texture on some devices (only galaxy s3 mini confirmed) i got dark area flickering on the texture as described in this thread:
Black Artifacts on Android in OpenGL ES 2
I'm not allowed to comment this thread (not enough credit) but I would like clarification from the author who solved this issue:
Could you explain a little more how you use glTexImage2D() and glTexSubImage2D() to solve this?
In code I got these lines to load the bitmaps:
(As you can see I'm using texImage2D to load the bitmap, the android documentation about gltexImage2D only provides attribute types but no explaination)
...
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
if (bitmap == null) {
if (LoggerConfig.ON) {
Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");
}
glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
glGenerateMipmap(GL_TEXTURE_2D);
...
edit:
tried to implement the solution according to the link in top but no luck, same flickering effect,
new code to load bitmap:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bitmap.getWidth() * bitmap.getHeight() * 4);
byteBuffer.order(ByteOrder.BIG_ENDIAN);
IntBuffer ib = byteBuffer.asIntBuffer();
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for(int i=0; i<pixels.length; i++){
ib.put(pixels[i] << 8 | pixels[i] >>> 24);
}
bitmap.recycle();
byteBuffer.position(0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.getWidth(), bitmap.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.getWidth(), bitmap.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, byteBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
Illustration of odd behavior, see the black area to the middle right in the image:
(I need 10 reputations just to post an image?!?!?)
https://dl.dropboxusercontent.com/u/61092317/blackflickering.jpg
The issue in the other question that you've referenced appears to be this: the author specified MipMaps when originally loading up his textures, and the MipMaps didn't work properly. There's a possibility that he wasn't using glTexImage2d properly.
The artifact that he got was: when the texture unit tried to move from one MipMap level to the next, there was no information there, and it rendered a blank (apparently black) texture instead. So, at least one level of his MipMap did get loaded.
I'm unsure if that author actually resolved his issue properly. OpenGL can be incredibly specific about how you load textures into it, and even the smallest error in your code can cause a problem. The problem may only occur on some platforms, so you get the impression that something is wrong with the device, but it still may be the code.
The best place to start is the Red Book: http://www.glprogramming.com/red/chapter09.html
And then: http://www.opengl.org/sdk/docs/man3/xhtml/glGenerateMipmap.xml
All of the functions that you want to know about are outlined there.
There's a few details that aren't answered in your question. Are you creating your own MipMaps by hand? Are you wanting OpenGL to generate MipMaps automatically?
I would suggest starting with a simple texture, that has no MipMaps, just one level. See if you can make that work. If that fixes the problem, then start moving towards using MipMaps.
As for the original question, there isn't exactly a difference between loading your texture data with glTexImage2D or updating texture data with glTexSubImage2D. glTexImage2D is used to specify a texture (ie: the width / height of the texture, which is different for each MipMap level), whereas glTexSubImage2D is used to update all or part of a texture that has already been specified by glTexImage2D. In the second instance, you're just updating it with new texels.

OpenGL ES 2.0 screen flickering

I'm facing a big problem. I'm using a Transformer tf101 tab with Android 4.0.3 on it.
My app is using a custom OpenGL ES 2.0 surface. I'm rendering multiple planes with textures. this textures are changing approx. 20 times per second and are updated by passing byte arrays. However, in certain cases, the screen begins flickering and does not render the new textures. Additional UI Elements are still responsive and do their work as intended. It seems the OpenGL context ignores all commands and is unresponsive.
When this happens, a few lines show up in my logCat:
08-20 10:31:15.390: D/NvOsDebugPrintf(2898): NvRmChannelSubmit: NvError_IoctlFailed with error code 1
followed by
08-20 10:31:15.390: D/NvOsDebugPrintf(2898): NvRmChannelSubmit failed (err = 13, SyncPointValue = 879005, returning = 0)
and a few of them:
08-20 10:31:15.390: D/NvOsDebugPrintf(2898): NvRmChannelSubmit failed (err = 196623, SyncPointValue = 0)
Here is how i create my Textured Plane:
m_nTextureStorage[0] = 0;
GLES20.glGenTextures( 1, m_nTextureStorage, 0);
GLES20.glActiveTexture( GLES20.GL_TEXTURE0 );
GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, m_nTextureStorage[ 0 ] );
// Set filtering
GLES20.glTexParameterf( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST );
GLES20.glTexParameterf( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST );
GLES20.glTexParameteri( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
GLES20.glTexParameteri( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );
and here is how i draw it:
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glActiveTexture( GLES20.GL_TEXTURE0 );
//GLES20.glUniformMatrix4fv(m_HMVPMatrixUniform, 1, false, mvpMatrix, 0);
GLES20.glUseProgram( m_nProgramHandle );
ByteBuffer oDataBuf = ByteBuffer.wrap( m_sTexture );
m_HTextureUniform = GLES20.glGetUniformLocation( m_nProgramHandle, "uTexture" );
m_HTextureCoordinate = GLES20.glGetAttribLocation( m_nProgramHandle, "TexCoordinate" );
GLES20.glUniform1iv( m_HTextureUniform, 2, m_nTextureStorage, 0 );
// get handle to the vertex shader's vPosition member
m_nPositionHandle = GLES20.glGetAttribLocation( m_nProgramHandle, "vPosition" );
// Prepare the triangle data
GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, 640, 480,
0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, oDataBuf );
// Prepare the triangle data
GLES20.glVertexAttribPointer( m_nPositionHandle, 3, GLES20.GL_FLOAT, false, 12, m_oVertexBuffer );
GLES20.glEnableVertexAttribArray( m_nPositionHandle );
GLES20.glVertexAttribPointer( m_HTextureCoordinate, 2, GLES20.GL_FLOAT, false, 12, m_oTextureBuffer);
GLES20.glEnableVertexAttribArray( m_HTextureCoordinate );
m_nMVPMatrixHandle = GLES20.glGetUniformLocation( m_nProgramHandle, "uMVPMatrix");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv( m_nMVPMatrixHandle, 1, false, mvpMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
The OpenGL Renderer simply calls the draw function of my TexturedPlane by passing the mvpMatrix. Im not deleting any textures since i've read that the android system will take care of that automatically.
I think it has something todo with the GPU going OOM, but im not sure since i haven't found anything related to the posted error messages.
Thanks it advance!
UPDATE:
The Rendermode was set to RENDER_WHEN_DIRTY . After Changing it to RENDERMODE_CONTINOUSLY the problem disappears.. Weird. Since this is just a workaround and no solution, I'm still asking for help ;)
Leaving the Rendermode at continuously is no option, since this consumes to much processor time and makes no sense, since rendering is only necessary when new textures a generated.
Finally found the solution.
When i call glFlush() after every rendering circle, it works fine. I no render every plane on a different texture channel, it works perfectly so far. Thanks for the help.
When you call ..:
GLES20.glUniform1iv( m_HTextureUniform, 2, m_nTextureStorage, 0 );
I think that binds a particular texture unit to the sampler uniform. But you're passing a texture name/object/handle/whatever (which is not a texture unit.) Maybe it's just coincidence, and you only ever pass in 0 (which is possibly the texture name in m_nTextureStorage,) which is coincidentally the correct/desired value?
Or maybe this results in the user-space driver crashing.

Framebuffer FBO render to texture is very slow, using OpenGL ES 2.0 on Android, why?

I am programming an Android 2d game using opengl es 2.0. After I draw my sprites to the backbuffer I draw lights to a FBO and try to blend it to the back buffer again.
When I draw the FBO to the framebuffer, even trasparent without any color, the framerates drops from 60 to 30 on a Samsung Galaxy w (it has an adreno 205 as gpu). I searched everywhere and tried everything, even if I draw a single sprite on the scene and blend a trasparent FBO texture to the screen the framerate drops. I tried other games with lighting effects on that phone and they run fine, almost every game is fine on that phone, I believe they use the framebuffer as well.
On the Galaxy SII (mali 400 gpu) runs fine, I am quite new to opengl so I believe I am making a mistake somewhere, I share my code.
// Create a framebuffer and renderbuffer
GLES20.glGenFramebuffers(1, fb, offset);
GLES20.glGenRenderbuffers(1, depthRb, offset);
// Create a texture to hold the frame buffer
GLES20.glGenTextures(1, renderTex, offset);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[offset]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
screenWidth, screenHeight, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
null);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
//bind renderbuffer
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthRb[offset]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
screenWidth, screenHeight);
// bind the framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[offset]);
// specify texture as color attachment
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, renderTex[offset], 0);
// specify depth_renderbufer as depth attachment
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, depthRb[0]);
// Check FBO status.
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if ( status == GLES20.GL_FRAMEBUFFER_COMPLETE )
{
Log.d("GLGame framebuffer creation", "Framebuffer complete");
}
// set default framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
I do this once on surface creation. Not sure if is correct. I keep the texture and framebuffer ids to switch to them when I need.
My drawing code:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
ShaderProgram program = glgame.getProgram();
//put vertices in the floatbuffer
mTriangleVertices.put(vertices, 0, len);
mTriangleVertices.flip();
GLES20.glVertexAttribPointer(program.POSITION_LOCATION, 2, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
//preparing parameter for texture position
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glEnableVertexAttribArray(program.POSITION_LOCATION);
//preparing parameter for texture coords
GLES20.glVertexAttribPointer(program.TEXTURECOORD_LOCATION, 2, GLES20.GL_FLOAT,
false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES,
mTriangleVertices);
//set projection matrix
GLES20.glEnableVertexAttribArray(program.TEXTURECOORD_LOCATION);
GLES20.glUniformMatrix4fv(program.MATRIX_LOCATION, 1, false, matrix, 0);
//draw triangle with indices to form a rectangle
GLES20.glDrawElements(GLES20.GL_TRIANGLES, numSprites * 6, GLES20.GL_UNSIGNED_SHORT,
indicesbuf);
//clear buffers
mTriangleVertices.clear();
mVertexColors.clear();
Everything is rendered on screen correctly, but the performance are ruined just when I draw the FBO texture.
Thank you very much for your help. I worked very hard on this and didn't find a solution.
According to qualcomm docs, you need to glclear after every glbindframebuffer, this is a problem related to tiled architecture, if you are switching framebuffers, data need to get copied from fastmem to normal memory to save current framebuffer and from slowmem to fast mem to get contents of newly binded frame, in case you are clearing just after glbind no data is copied from slowmem to fastmem and you are saving time, but you need to redesign your render pipeline often, so it will avoid reading data back and forth between slow and fast memory, so try to do glclear after each bind and it should help, you can also use adreno profiler to get additional information about problematic calls, but i doubt it will help with adreno200 i am trying to get two buffers for blur and i am ending with 10fps, bindframebuffer call can take up to 20msec if its not cleared, if it is it should end at 2ms.

Categories

Resources