Warm tip: This article is reproduced from stackoverflow.com, please click
java opengl

glTexSubImage2D() causing EXCEPTION_ACCESS_VIOLATION (0xc0000005) despite passed data buffer being c

发布于 2020-03-27 10:23:45

I am trying to copy image data provided by JCEF as a ByteBuffer to an OpenGL texture. When I pass the provided ByteBuffer to the glTexSubImage2D() call with the appropriate parameters the application crashes with EXCEPTION_ACCESS_VIOLATION (0xc0000005).

What I'm trying to do:

JCEF is a Java wrapper for CEF (Chromium Embedded Framework) and I am currently using the offscreen rendering model (as I want to render the browser in a 3d game using opengl).

JCEF provides updated screen data from the browser through an onPaint method with a list of dirty rectangles and a ByteBuffer with the updated screen data.

I want to copy that ByteBuffer onto an OpenGL Texture using glTexSubImage2D or similar.

I have confirmed that all values passed to the function call are valid and that the buffer provided to me by JCEF is long enough. glGetError() calls after each OpenGL library call leading up to the crash return 0.

My opengl library is from LightWeight Java Game Library 3 (LWJGL3) and I have used it in all of my other code.

Searches on other threads and questions both here and on various other forums have all pointed to the ByteBuffer passed to OpenGL glTexSubImage2D() being too short. I have double and triple checked this and this is not the case. (As far as my understanding of OpenGL and its interpretation of data formats and types tell me.)

First some basic classes. This code is part of a Rendering Engine so I will only include what I feel is relevant but I am happy to attach more if needed.

The first class is a PreloadedTexture class. This represents a texture that has not come directly from a file but rather has been modified or generated in some way. It is my wrapper for an OpenGL Texture2D.

package com.base.shader;

import java.nio.ByteBuffer;

import static org.lwjgl.opengl.GL45.*;

public class PreloadedTexture {
    int width;//The width of the texture in pixels
    int height;//The height of the texture in pixels
    int handle;//The OpenGL handle for the texture. This is used to reference this texture in other OpenGL calls

    /*
     This constructor creates a blank texture with the dimensions specified.
    */
    public PreloadedTexture(int width, int height) {
        super(Math.random() + "");
        this.width = width;
        this.height = height;
        this.handle = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, handle);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

    }

    /*EDIT: This method was renamed and had the internals changed to not bind the texture to a uniform sampler as mentioned in my comment below. It now just does a call to glBindTexture() as the uniform binding would occasionally return errors.
    */
    public void bind(){
        glBindTexture(GL_TEXTURE_2D, handle);
    }
}

The BrowserWindow class is where most of the action happens but I have removed everything that isn't related to this problem.

package com.base.game;

import com.base.shader.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.awt.*;
import java.util.concurrent.LinkedTransferQueue;

import static org.lwjgl.opengl.GL46.*;

public class BrowserWindow {
    private PreloadedTexture texture;//The texture that should be copied to.
    private ByteBuffer imgData;//The BytBuffer provided by CEF with the updated screen data.
    private LinkedTransferQueue<Rectangle> dirtyRects = new LinkedTransferQueue<>();//The list of dirty rects. The reasoning behind a LinkedTransferQueue is explained below.
    int width;//The width of the browser view in pixels.
    int height;//The height of the browser view in pixels.

    /*
     * Creates a browser window with the specified width and height. Initialises a ByteBuffer with image data to draw and a texture to draw to.
    */
    public BrowserWindow(int _w, int _h){
        width = _w;
        height = _h;

        imgData = ByteBuffer.allocate(width * height * 4 * Integer.BYTES);//This is later filled by the JCEF code but I have removed that as I know it works.
        //The ByteBuffer is filled with bytes representing the screen data in BGRA format. Each Byte is a separate component of a pixel. The pixels are grouped together.
        //This results in a structure like so: [B,G,R,A,B,G,R,A,etc.] I have maually checked that this data looks valid by going through portions of it and seeing understandable variances in the values.
        texture = new PreloadedTexture(width, height); // An instance of the above PreloadedTexture class. The final destination for the imgData above.
    }


    //Called when the engine whats to render a frame. This should update the texture of the browser and then render the mesh with this texture. The actual mesh rendering code is ommited because again I know that works and it is not where the issue lies.
    public void render(ShaderProgram shaderProgram, Camera camera) {
        synchronized (imgData) { 
          //This block is to allow communication with the CEF thread. 
          //CEF runs the browser in a seperate thread and the onPaint method is called from there.
          //OpenGL is not initialised in that thread however so I have used a LinkedTransferQueue<Rectangle> to transfer the dirty portions that need to be redrawn and the updated ByteBuffer is transferred byt directly setting it on the class.
          //Again the actual code that does this has been ommitted as I know by checking the values manually that the data that is transfered is correct.

            if (dirtyRects.size() > 0) {
              //This just checks that there are dirty portions to update otherwise the call would waste both CPU and GPU time.
                ArrayList<Rectangle> rects = new ArrayList<>();
                //This provides a list to drop the Rectangles into for ease of access. These rectangles aren;t used at the moment but their implementation can be seen in the below commented out code.
                dirtyRects.drainTo(rects);
                imgData.rewind();
                //This ensures that the imgData buffer is at the beggining.
                texture.bind();
                //Here I bind the texture to GL_TEXTURE_2D. The actual implementation is shown above in PreloadedTexture class.
                System.out.println("Pre Render Error: " + glGetError());
                //This checks to see if OpenGL was in an invalid state before and also clears the error buffer to ensure any errors reported from now on actually occur in this method.
                System.out.println(glGetTexLevelParameteri(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) + ", " + glGetTexLevelParameteri(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT));
                //This is a sanity check to ensure that the texture was initialised correctly in the PreloadedTexture constructor.
                System.out.println(imgData.limit() + ", " + width * height * 4);
                //This is another sanity check to ensure that the ByteBuffer is actually large enough. At the moment I have given it extra data to work with to see if it was the problem, hence the much larger limit of the ByteBuffer compared to the computed nescesary amount.
                glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
                glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
                //These two calls reset the OpenGL state to ensure that rows and pixels aren't being skipped.
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, imgData);//This line results in the EXCEPTION_ACCESS_VIOLATION (0xc0000005)
                System.out.println("Render Error: " + glGetError()); //This is a sanity check from before with another problem that I fixed.
                //Below is the implementation that made use of the rectangles. It follows a similar pattern to the above code but only updates the specified rectangles.
//                for (Rectangle rect : rects) {
//                    System.out.println(rect.x + ", " + rect.y + ", " + rect.width + ", " + rect.height);
//                    glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x);
//                    glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y);
//                    glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.width, rect.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,imgData);
//                }
            }

            imgData.notifyAll();//This notifies the CEF thread that the Rectangles and imgData objects are now free.
        }

        //Mesh rendering would happen here but again it has been omitted for conciseness this is not where the problem lies.
    }
}

As I mentioned in the code comments I have double checked that the data from CEF is in a valid format. It follows a 4 byte repeating sequence of BGRA values. The ByteBuffer is plenty long enough as is shown by the sanity checks before the call to glTexSubImage2D();

Sorry for the amount of code but as I said earlier it is part of a rendering engine and much of the structure is influenced by that fact.

I expect the call to glTexSubImage2D() to copy the ByteBuffer data onto the OpenGL Texture2D however the call results in a EXCEPTION_ACCESS_VIOLATION (0xc0000005). The ByteBuffer is long enough and the data inside is in a valid format. I have also tried swapping GL_UNSIGNED_INT_8_8_8_8_REV with GL_UNSIGNED_INT_8_8_8_8 and GL_UNSIGNED_BYTE but they do not affect the outcome. When looking at the logs the exception occurs because it tried to access address 0x0000000000000010.

Does anyone know what might be causing this error. All my research has said that the ByteBuffer is too short, however, as I have said I have double checked this. Is my understanding of what is going on flawed or is this something else?

Questioner
Jacob McEwan
Viewed
56
Jacob McEwan 2019-07-04 20:31

After some poking around in the error logs and looking at library sources I traced down the error to how I was allocating the ByteBuffer. The OpenGL library made use of a memory offset accessor (fairly obvious as to why), but the method of obtaining the offset expects the ByteBuffer to be direct. Changing the BrowserWindow class to instead use a direct buffer has fixed the error.

The updated portion is listed below for anyone else with a similar issue.

//This is the updated constructor from the BrowserWindow class
public BrowserWindow(int _w, int _h){
        width = _w;
        height = _h;

        //imgData = ByteBuffer.allocate(width * height * 4 * Integer.BYTES); This is the old line that created a non-direct ByteBuffer.
        imgData = ByteBuffer.allocateDirect(width * height * 4);//This is the new line. Note the allocateDirect() call and the removal of the extra space in the buffer in the form of Integer.BYTES.
        texture = new PreloadedTexture(width, height);
    }