调用OpenGL纹理的CGContext绘图函数时触发EXC_BAD_ACCESS故障排查
EXC_BAD_ACCESS when Drawing to CGContext for OpenGL Texture (Swift/Cocoa)
你的问题根源在于CGContext的缓冲区参数不匹配以及后续OpenGL纹理创建的错误配置,这些问题导致看似有效的CGContext实际处于无效状态,触发了内存访问错误。下面一步步拆解问题并给出修复方案:
核心问题分析
1. 缓冲区类型与bitsPerComponent不匹配
你分配了UnsafeMutablePointer<CGFloat>作为绘图缓冲区,但指定了bitsPerComponent: 8——CGFloat是4字节(64位系统下),而8位分量需要的是1字节的UInt8类型缓冲区。这种类型不匹配会导致CGContext写入内存时越界,直接触发EXC_BAD_ACCESS。
2. bytesPerRow计算错误
代码里使用了未定义的floatsPerPixel(应该是笔误的floatsPerPoint),但即使是floatsPerPoint也不符合要求:当使用8位分量、RGBA4通道时,每个像素占4字节,bytesPerRow应该是bitmapColumns * 4,而非基于浮点数的计算。
3. CGContext初始化的指针传递错误
CGContext(data:)参数需要的是UnsafeMutableRawPointer?,你已经持有UnsafeMutablePointer<CGFloat>,不需要额外加&(加&会将指针转为指针的指针,导致CGContext访问错误的内存地址)。
4. OpenGL纹理参数不匹配
glTexImage2D的宽高写死为256,但实际缓冲区是10x10,会导致读取超出缓冲区范围的内存- 格式参数不匹配:你创建的是RGBA格式的CGContext,但
glTexImage2D用了GL_RGB内部格式和GL_UNSIGNED_INT类型,正确配置应该匹配RGBA8位的缓冲区 glTexImage2D放在if let context代码块外,即使CGContext初始化失败也会执行,导致访问无效缓冲区
修复后的代码
import Cocoa import CoreGraphics import OpenGL.GL3 class MyView: NSOpenGLView { var textureID: GLuint = 0 // 修正命名:TBO是纹理缓冲对象,这里是普通纹理ID override func prepareOpenGL() { super.prepareOpenGL() // 务必调用父类方法初始化OpenGL环境 let bitmapWidth = 10 let bitmapHeight = 10 let bytesPerPixel = 4 // RGBA各8位,共4字节 let totalBytes = bitmapWidth * bitmapHeight * bytesPerPixel // 分配正确类型的缓冲区:UInt8对应8位分量 var bitmapBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: totalBytes) // 初始化缓冲区为透明黑色,避免垃圾数据干扰 memset(bitmapBuffer, 0, totalBytes) if let context = CGContext( data: bitmapBuffer, width: bitmapWidth, height: bitmapHeight, bitsPerComponent: 8, bytesPerRow: bitmapWidth * bytesPerPixel, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue ) { // 绘图操作现在可以正常执行 context.setFillColor(red: 0, green: 0, blue: 1, alpha: 0.5) context.fill(CGRect(x: 0, y: 0, width: 5, height: 5)) context.setFillColor(red: 1, green: 0, blue: 1, alpha: 0.5) context.fill(CGRect(x: 5, y: 5, width: 5, height: 5)) context.setFillColor(red: 0, green: 1, blue: 0, alpha: 0.5) context.fill(CGRect(x: 0, y: 5, width: 5, height: 5)) context.setFillColor(red: 0.5, green: 0, blue: 0.5, alpha: 0.5) context.fill(CGRect(x: 5, y: 0, width: 5, height: 5)) // 创建并配置OpenGL纹理 glGenTextures(1, &textureID) glBindTexture(GLenum(GL_TEXTURE_2D), textureID) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT) // 将CGContext缓冲区数据上传到OpenGL纹理 glTexImage2D( GLenum(GL_TEXTURE_2D), 0, GLint(GL_RGBA8), // 内部格式匹配RGBA8位 bitmapWidth, bitmapHeight, 0, GLenum(GL_RGBA), // 像素格式匹配CGContext的RGBA GLenum(GL_UNSIGNED_BYTE), // 类型匹配UInt8缓冲区 bitmapBuffer ) } // 确保纹理上传完成后再释放缓冲区 bitmapBuffer.deallocate() } }
额外说明
- 为什么LLDB里
po context!能看到地址但调用方法失败?因为CGContext对象本身是存在的,但它指向的缓冲区是无效的(类型不匹配+指针传递错误),当它尝试写入缓冲区时就会触发内存访问错误。 - 后续如果需要在
drawRect(_:)中操作,只要保证CGContext的参数和缓冲区类型匹配,同样可以正常运行。
内容的提问来源于stack exchange,提问作者Buggus Mageevers
相关产品推荐
相关产品推荐

