温馨提示:本文翻译自stackoverflow.com,查看原文请点击:java - glTexSubImage2D() causing EXCEPTION_ACCESS_VIOLATION (0xc0000005) despite passed data buffer being c

java - glTexSubImage2D()导致EXCEPTION_ACCESS_VIOLATION(0xc0000005)尽管传递的数据缓冲区为c

发布于 2020-03-27 11:17:25

我正在尝试将JCEF提供的图像数据作为ByteBuffer复制到OpenGL纹理。当我使用适当的参数将提供的ByteBuffer传递给glTexSubImage2D()调用时,应用程序因EXCEPTION_ACCESS_VIOLATION(0xc0000005)崩溃。

我正在尝试做的是:

JCEF是用于CEF(铬嵌入式框架)的Java包装程序,我目前正在使用屏幕外渲染模型(因为我想使用opengl在3d游戏中渲染浏览器)。

JCEF通过onPaint方法从浏览器中提供了更新的屏幕数据,该方法包含一个脏矩形列表和一个ByteBuffer,其中包含更新的屏幕数据。

我想使用glTexSubImage2D或类似方法将该ByteBuffer复制到OpenGL纹理上。

我已经确认传递给函数调用的所有值都是有效的,并且JCEF提供给我的缓冲区足够长。在导致崩溃的每个OpenGL库调用之后,调用glGetError()都会返回0。

我的opengl库来自LightWeight Java Game Library 3(LWJGL3),在所有其他代码中都使用了它。

在此处以及在其他各个论坛上对其他线程和问题的搜索都表明,传递给OpenGL glTexSubImage2D()的ByteBuffer太短了。我已经检查过两次和三次,事实并非如此。(据我对OpenGL的理解以及对数据格式和类型的解释所知。)

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();

很抱歉,代码量很多,但是正如我之前说的,它是渲染引擎的一部分,很多结构都受该事实影响。

我希望对glTexSubImage2D()的调用将ByteBuffer数据复制到OpenGL Texture2D上,但是该调用会导致EXCEPTION_ACCESS_VIOLATION(0xc0000005)。ByteBuffer足够长,并且内部数据采用有效格式。我也尝试过将GL_UNSIGNED_INT_8_8_8_8_REV与GL_UNSIGNED_INT_8_8_8_8和GL_UNSIGNED_BYTE交换,但它们不会影响结果。当查看日志时,发生异常是因为它想访问地址0x0000000000000010。

有谁知道是什么原因导致此错误。我所有的研究都说ByteBuffer太短了,但是正如我所说的,我已经仔细检查过这一点。我对正在发生的事情的理解存在缺陷吗?还是其他?

查看更多

查看更多

提问者
Jacob McEwan
被浏览
50
Jacob McEwan 2019-07-04 20:31

在浏览错误日志并查看库源代码后,我将错误归结为如何分配ByteBuffer。OpenGL库使用了内存偏移量访问器(对于原因,这一点很明显),但是获取偏移量的方法期望ByteBuffer是直接的。更改BrowserWindow类以改为使用直接缓冲区可解决此错误。

下面列出了有类似问题的其他任何人的更新部分。

//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);
    }