蛇形遍历数组, 我的思路是使用两个点坐标再加上一个方向变量, 两个点同时在边缘上移动, 然后连线.
但是, 其实这个问题本质就是从一点出发, 不断地 walk, 一次只 walk 一步, 需要保留方向状态. 如果一次 walk 完斜线, 则不用方向状态, 因为可以根据 walk 的起点来判断.
蛇形遍历数组, 我的思路是使用两个点坐标再加上一个方向变量, 两个点同时在边缘上移动, 然后连线.
但是, 其实这个问题本质就是从一点出发, 不断地 walk, 一次只 walk 一步, 需要保留方向状态. 如果一次 walk 完斜线, 则不用方向状态, 因为可以根据 walk 的起点来判断.
现代计算机系统在进行图像处理时,可以利用 CPU 或者显卡两种芯片之一进行处理,也可以同时使用两者。本文讨论在 macOS 上的图像处理。
1. 完全使用 CPU 进行图像处理
图像(如 png)从硬盘中读取并解压到内存中,之后的图像处理完全用 CPU 操作内存,和显卡无关。
2. 完全使用显卡进行图像处理
图像(如 png)从硬盘中读取并解压到内存中,然后传到显卡显存中(创建 OpenGL texture),图像一旦传到显卡,内存中的图像数据就可以删除了。
3. 同时使用 CPU 和显卡进行图像处理
图像同时存于内存以及显存中,有时利用显卡进行处理,有时利用 CPU 进行处理。因为图像数据同时保存于内存和显存中,所以需要某种绑定机制,关联内存中的图像和显存中的图像,并在一方变更时,更新另一方。
# CVPixelBufferRef 与 CVOpenGLTextureRef
CVPixelBufferRef 表示内存中的图像。
CVOpenGLTextureRef 表示显存中的图像,内部使用 OpenGL 的 texture。
苹果的 Core Video 框架提供了显存图像到内存图像的单向实时绑定机制,也即绑定 CVPixelBufferRef 和 CVOpenGLTextureRef,当后者(显存)更新时,框架自动更新前者(内存)。注意,这种实时绑定是单向的,更新的传导只有一个方向,即从显存到内存。
使用 CVOpenGLTextureCacheCreateTextureFromImage() 函数来建立这种绑定关系。绑定建立后,用户(也即你的代码)对显存或者内存的操作,都必须涉及到锁,因为框架本身同时也会操作这两份数据。
涉及到多者同时操作一份数据的情况,都需要锁。
使用 CVPixelBufferLockBaseAddress() 和 CVPixelBufferUnlockBaseAddress() 两个函数进行锁操作。显然,当你的代码要操作内存数据时,你应该 lock CVPixelBufferRef,当你想操作显存数据时,就应该 unlock CVPixelBufferRef。
前面提到,这种单向绑定会将显存的更新传导到内存,所以,当你更新完显存(即执行 OpenGL glFinish 操作,文档提到是 glFlush 之后)之后,你就获得了 OpenGL 渲染的图像数据(OpenGL 截图),其内部实现应该是用 glReadPixels() 或者 glGetTexImage() 函数。这时,你就可以把 OpenGL 渲染的结果保存成 png 文件了。
# 关于 Metal 框架的 CVMetalTextureRef
CVMetalTextureRef 是用来替代 CVPixelBufferRef 的,因为苹果已经发布了 Metal 框架用来替代所有苹果操作系统上的 OpenGL。
提一句,从面向对象的角度 CVPixelBufferRef CVOpenGLTextureRef CVMetalTextureRef 这三者都是 CVImageBufferRef 的子类。不过,在 C 语言里,这三者是同一个结构体,只不过用 typedef 命了 4 个名字而已。
typedef CVImageBufferRef CVPixelBufferRef; typedef CVImageBufferRef CVOpenGLTextureRef; typedef CVImageBufferRef CVMetalTextureRef;
CVImageBufferRef(也即 CVBufferRef)内部应该是用了 type 字段来表示不同的子类,毕竟 C 语言没有 C++ 那样的类和继承,只能用结构体来实现子类,都是惯用法,大同小异。
如果你使用 Core Graphics, 那么从内存到显存的路径是这样的: CGImage => CGBitmapContext => OpenGL texture
libtool -static -o new.a old1.a old2.a
glReadPixels 报错:
glGetError() 返回 GL_INVALID_OPERATION(1282, 0x0502)
原因是 Multisample storage 的 framebuffer 无法被读取。所以,应该先 blit 到非 Multisample 的 fbo 之后再读取。
见:https://www.khronos.org/opengl/wiki/GL_EXT_framebuffer_multisample
如果你的 OC 项目引入了一个 C++ 静态库, 那么编译时会提示找不到 vtable 之类的的错误, 因为 OC 是 C 语言, 无法识别 C++ 的静态库, 解决方法是让 Xcode 认为你在使用 C++/Objective-C++, 假装也行.
所以解决方法是:
面试了很多做了多年网络编程的人, 从TCP socket中读取报文这项基本技能, 许多人都做不对. 经典的错误用法是:
char buf[1024]; // 1024或者更大 read(sock, buf, sizeof(buf)); if(parse(buf) == 1){ // 报文解析完毕 }else{ // 不是一个完整的报文, 丢弃 }
这是非常经典的错误! 没有任何文档或者手册表明, read()会读到*完整*的报文, 对于read()函数来说, 它只知道字节流, 不知"报文"为何物. read()可能只读到了1个字节的数据就返回... 而且, read()返回的数据的长度, 和对方write()不是一一对应的. 对方调用1次write(), 可能本方要调用1次或者2次或者更多次read().
这个错误之所以经典, 是因为在局域网条件下, 且报文非常小的情况下, 一般(仅仅是一般, 不是100%)write()和read()是一一对应的, 所以, 有些人即使是在BAT公司写了10年网络编程, 也发现不了这个错误. 这个错误就是所谓的"TCP粘包/断包".
看看这个PDF: 高性能并发网络服务器设计与实现.pdf, 看看如何正确地从TCP socket中读取报文. 或者看看 Sim C++ 框架的源码, 了解下 Sim 是如何从 TCP socket 中读取一个报文: https://github.com/ideawu/sim/blob/master/src/server.cpp#L143