将 GL 纹理复制到另一个 GL 纹理或 GL 像素缓冲区,以及从 GL 像素缓冲区复制





5.00/5 (2投票s)
如何使用 GL 的像素缓冲区对象 (PBO) 将一个纹理复制到另一个纹理
引言
我想重构我的基于 FreeType 的 文本渲染器 "FreeTypeGlyphWise" 和 "FreeTypeLineWise",用于 OpenGL/OpenTK(在 Linux 上使用 Mesa 库)。目标不是一次性分配用于渲染所有字体面和字体大小的字形纹理,而是分配一个初始字形集纹理,并在需要时(缓存更多字形时)增加纹理大小。
我认为保留和调整纹理大小的要求是普遍的,但在网络上找不到现成的文章或论坛答案。因此,我想分享我收集到的知识。
背景
OpenGL 纹理位于驱动程序/GPU 控制的内存中。为了保留和调整纹理大小,应该使用一种完全在驱动程序/GPU 控制的内存中工作的技术(希望避免驱动程序/GPU 控制的内存与主/CPU 控制的内存之间的数据传输),以获得良好的性能。
在继续阅读之前,我想声明我不是 OpenGL/OpenTK 专业人士。我将在此描述的解决方案对我来说非常有效,并且有可能成为 OpenGL 3.1 之前的通用备选方案。
据我所知(在我进行网络搜索后),有几种方法可以实现这一点:
- 纹理复制:对于支持 ARB_copy_image 扩展(或较旧的 NV_copy_image 扩展)的 OpenGL 4.3 及以上版本,您可以使用
glCopyImageSubData()
。 - 帧缓冲区复制 1/FBO 绘制:对于支持 ARB_framebuffer_object 扩展的 OpenGL 3.1 及以上版本,您可以将源纹理附加到一个 FBO,将目标纹理附加到另一个 FBO,设置 FBO 的读取缓冲区和绘制缓冲区,从源附件读取并绘制到目标附件,然后使用
glBlitFramebuffer()
将一个帧缓冲区绘制到另一个。 - 帧缓冲区复制 2/FBO 复制:此外,还有其他几种方法可以通过帧缓冲区对象 (FBO) 将源纹理复制到目标纹理。通常,您需要支持 ARB_framebuffer_object 扩展的 OpenGL 3.1 及以上版本。请阅读 此 论坛线程以了解这些替代方案的概述和性能指标。
- 像素缓冲区复制:这是我想介绍的方法(因为像素缓冲区保证是 OpenGL 2.1 及以上版本的一部分)...
- 位图复制:将源纹理从驱动程序/GPU 控制的内存下载到主/CPU 控制的内存,然后再上传回驱动程序/GPU 控制的内存。这是我想避免的!
我的环境是 MonoDevelop 5.10(也可能使用旧版本)在 openSuse Leap 42.1 64 位上,使用 Mesa 库。我的 Linux 运行在 VMWare Player 上。从 GL.GetString(StringName.Version)
获取的环境版本字符串是 "2.1 Mesa 10.3.7
"。
因此,我专注于一种保证适用于 OpenGL 2.1 及以上版本的解决方案。
Using the Code
我使用一个包装类 FtTexture
来管理 GL 纹理,包括纹理元数据。这个类提供了 CreateAndBindPhysicalTexture()
方法。
/// <summary>
/// Creates and binds a new physical texture based on the actual pixel format and physical size.
/// </summary>
/// <param name="data">Pointer to the raw data to use. Can be <c>System.IntPtr.Zero</c>.</param>
/// <returns>The GL texture identifier.</returns>
public int CreateAndBindPhysicalTexture(System.IntPtr data)
{
#if DEBUG
string methodName = "CreateAndBindPhysicalTexture";
#endif
// Make sure that extensions are initialized (edge_clamp and sRGB extensions doesn't need new
// function pointers, but call it anyway).
GlUtil.VersionInfo.Current.EnsureExtensionsInit();
// Check whether one of the extensions "GL_SGIS_texture_edge_clamp" or
// "GL_EXT_texture_edge_clamp" is supported.
// This will prevent the clamp algorithm from using the border texel/border color for sampling.
textureEdgeClamp = GlUtil.VersionInfo.Current.GLEXT_SGIS_texture_edge_clamp ||
GlUtil.VersionInfo.Current.GLEXT_EXT_texture_edge_clamp;
int clampToEdge = (GlUtil.VersionInfo.Current.GLEXT_SGIS_texture_edge_clamp ?
(int)GlUtil.Globals.GL_CLAMP_TO_EDGE_SGIS :
(GlUtil.VersionInfo.Current.GLEXT_EXT_texture_edge_clamp ?
(int)GlUtil.Globals.GL_CLAMP_TO_EDGE_EXT :
(int)All.ClampToEdge));
textureSrgb = GlUtil.VersionInfo.Current.GLEXT_texture_sRGB;
if (!textureEdgeClamp && !clampWarned)
{
Console.WriteLine("WARNING: No edge clamp extension available. Texture clamping " +
"involves the border textel/color and might as they have artifacts.");
clampWarned = true;
}
if (!textureSrgb && !srgbWarned)
{
Console.WriteLine("WARNING: No sRGB color space extension available. Colors don't " +
"reach the best quality available for display on monitors.");
srgbWarned = true;
}
this.Id = GL.GenTexture();
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.GenTexture");
#endif
GL.BindTexture(TextureTarget.Texture2D, this.Id);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.BindTexture");
#endif
GL.TexImage2D (TextureTarget.Texture2D, 0, this.PxInternalFormat,
this.PhysicalWidth, this.PhysicalHeight, 0, this.PxFormat, this.PxType, data);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.TexImage2D");
#endif
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
_isRepeated ? (int)TextureWrapMode.Repeat :
(textureEdgeClamp ? clampToEdge : (int)TextureWrapMode.Clamp));
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.TexParameter-1");
#endif
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
_isRepeated ? (int)TextureWrapMode.Repeat :
(textureEdgeClamp ? clampToEdge : (int)TextureWrapMode.Clamp));
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.TexParameter-2");
#endif
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
_isSmooth ? (int)TextureMagFilter.Linear : (int)TextureMagFilter.Nearest);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.TexParameter-3");
#endif
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
_isSmooth ? (int)TextureMinFilter.Linear : (int)TextureMinFilter.Nearest);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtTexture).Name, methodName, "GL.TexParameter-4");
#endif
return this.Id;
}
为了确保 GL 调用顺序正确且使用允许的参数,我在每个 GL API 调用后调用 GlUtil.Globals.CheckError()
。为了避免负面的运行时影响,CheckError()
调用仅为 DEBUG
模式目标编译。错误检查如下:
/// <summary>
/// Check and report details for known GL error types.
/// </summary>
/// <param name="className">The class wherein the error occurs.</param>
/// <param name="methodName">The method name wherein the error occurs.</param>
/// <param name="callName">The call that causes the error.</param>
/// <remarks>
/// With .NET 4.5 it is possible to use equivalents for C/C++ <c>__FILE__</c> and <c>__LINE__</c>.
/// On older .NET versions we use <c>className</c>, <c>methodName</c> and <c>callName</c> instead.
/// </remarks>
internal static void CheckError(string className, string methodName, string callName)
{
int errorCode = (int)GL.GetError();
if(errorCode == 0)
return;
string error = "Unknown error";
string description = "No description";
if (errorCode == GL_INVALID_ENUM)
{
error = "GL_INVALID_ENUM";
description = "An unacceptable value has been specified for an enumerated argument.";
}
else if (errorCode == GL_INVALID_VALUE)
{
error = "GL_INVALID_VALUE";
description = "A numeric argument is out of range.";
}
else if (errorCode == GL_INVALID_OPERATION)
{
error = "GL_INVALID_OPERATION";
description = "The specified operation is not allowed in the current state.";
}
else if (errorCode == GL_STACK_OVERFLOW)
{
error = "GL_STACK_OVERFLOW";
description = "This command would cause a stack overflow.";
}
else if (errorCode == GL_STACK_UNDERFLOW)
{
error = "GL_STACK_UNDERFLOW";
description = "This command would cause a stack underflow.";
}
else if (errorCode == GL_OUT_OF_MEMORY)
{
error = "GL_OUT_OF_MEMORY";
description = "There is not enough memory left to execute the command.";
}
else if (errorCode == GL_INVALID_FRAMEBUFFER_OPERATION)
{
error = "GL_INVALID_FRAMEBUFFER_OPERATION";
description = "The object bound to FRAMEBUFFER_BINDING is not 'framebuffer complete'.";
}
else if (errorCode == GL_CONTEXT_LOST)
{
error = "GL_CONTEXT_LOST";
description = "The context has been lost, due to a graphics card reset.";
}
else if (errorCode == GL_TABLE_TOO_LARGE)
{
error = "GL_TABLE_TOO_LARGE";
description = "The exceeds the size limit. This is part of the " +
"(Architecture Review Board) ARB_imaging extension.";
}
Console.WriteLine ("An internal OpenGL call failed in class '" + className + "' " +
"method '" + methodName + "' call '" + callName + "'. " +
"Error '" + error + "' description: " + description);
}
我评估的错误是:
/// <summary>
/// The GL call return value 'NO ERROR'.
/// </summary>
public static int GL_NO_ERROR = 0;
/// <summary>
/// The GL call return value 'INVALID ENUM'.
/// </summary>
public static int GL_INVALID_ENUM = 0x0500;
/// <summary>
/// The GL call return value 'INVALID VALUE'.
/// </summary>
public static int GL_INVALID_VALUE = 0x0501;
/// <summary>
/// The GL call return value 'INVALID OPERATION'.
/// </summary>
public static int GL_INVALID_OPERATION = 0x0502;
/// <summary>
/// The GL call return value 'STACK OVERFLOW'.
/// </summary>
public static int GL_STACK_OVERFLOW = 0x0503;
/// <summary>
/// The GL call return value 'STACK UNDERFLOW'.
/// </summary>
public static int GL_STACK_UNDERFLOW = 0x0504;
/// <summary>
/// The GL call return value 'OUT OF MEMORY'.
/// </summary>
public static int GL_OUT_OF_MEMORY = 0x0505;
/// <summary>
/// The GL call return value 'INVALID FRAMEBUFFER OPERATION'.
/// </summary>
public static int GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506;
/// <summary>
/// The GL call return value 'CONTEXT LOST'.
/// </summary>
public static int GL_CONTEXT_LOST = 0x0507;
/// <summary>
/// The GL call return value 'TABLE TOO LARGE'.
/// This is part of the (Architecture Review Board) ARB_imaging extension.
/// </summary>
public static int GL_TABLE_TOO_LARGE = 0x8031;
实际的保留和调整大小任务由以下代码实现。当前的纹理元数据存储在 _glyphsTexture
中。
...
// Ensure to perform two-dimensional texturing (unless three-dimensional or
// cube-mapped texturing is also enabled).
GL.Enable(EnableCap.Texture2D);
// Copy is done here via PBO (pixel buffer object);
int pboID;
GL.GenBuffers(1, out pboID);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.GenBuffers");
#endif
// Select the target "buffer" PBO.
GL.BindBuffer(BufferTarget.PixelPackBuffer, pboID);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BindBuffer-1");
#endif
// Allocate the target "buffer" PBO pixel memory (by delivering a 'System.IntPtr.Zero'
// pointer).
System.IntPtr data = System.IntPtr.Zero;
GL.BufferData(BufferTarget.PixelPackBuffer, (System.IntPtr)_glyphsTexture.RequiredMemoryUsage,
data, BufferUsageHint.StreamDraw);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BufferData");
#endif
// Select the source "texture".
GL.BindTexture(TextureTarget.Texture2D, _glyphsTexture.Id);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BindTexture");
#endif
// The 'pixels' are not a pointer to client memory but an offset in bytes into target "buffer"
// PBO, if target is GL_PIXEL_PACK_BUFFER!
System.IntPtr pixels = System.IntPtr.Zero;
// Return the currently selected source into the currently selected target, which copies
// source "texture" into "buffer" PBO.
GL.GetTexImage(TextureTarget.Texture2D, 0, _glyphsTexture.PxFormat, _glyphsTexture.PxType,
pixels);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.GetTexImage");
#endif
// The "buffer" PBO must be unbound before the target "texture" creation.
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BindBuffer-2");
#endif
// Create the target "texture" - create metadata.
FtTexture newGlyphsTexture = new FtTexture(_glyphsTexture.PhysicalWidth,
_glyphsTexture.PhysicalHeight * 2, 0, 2);
// Create the target "texture" - set pixel format synchronously to the source.
newGlyphsTexture.SetPixelFormat(_glyphsTexture.PxInternalFormat, _glyphsTexture.PxFormat,
_glyphsTexture.PxType);
// Create the target "texture" - create and bind the physical texture.
newGlyphsTexture.CreateAndBindPhysicalTexture(IntPtr.Zero);
// Select the source "buffer" PBO.
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, pboID);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BindBuffer-3");
#endif
// Return the currently selected source into the currently selected target, which copies
// "buffer" PBO into target "texture".
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, _glyphsTexture.PhysicalWidth,
_glyphsTexture.PhysicalHeight, newGlyphsTexture.PxFormat, newGlyphsTexture.PxType,
System.IntPtr.Zero);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.TexSubImage2D");
#endif
// Unbind the "buffer" PBO.
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.BindBuffer-4");
#endif
// Free the "buffer" PBO.
GL.DeleteBuffers(1, new int[] {pboID});
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.DeleteBuffers");
#endif
// Free source "texture".
GL.DeleteTextures (1, new int[] {_glyphsTexture.Id});
#if DEBUG
GlUtil.Globals.CheckError(typeof(FtFont).Name, "ctr", "GL.DeleteTextures");
#endif
// Done with two-dimensional texturing.
GL.Enable(EnableCap.Texture2D);
// Logically replace source "texture" with target "texture".
_glyphsTexture = newGlyphsTexture;
...
就是这样!
关注点
我希望这能帮助您找到适合您需求的解决方案。
现在我可以继续重构我的基于 FreeType 的 文本渲染器 "FreeTypeGlyphWise" 和 "FreeTypeLineWise",并希望我能很快提供一个更快的解决方案。
历史
- 2018年10月10日 - 初始版本