2025-11-12 12:35:26    2    0    0

GLFW

  • 这里我们使用cmake从源码编译GLFW:
  • 下载源码: GLFW官网:https://www.glfw.org/download.html);
  • 常规编译即可,记得勾选"BUILD_SHARED_LIBS";
  • 编译完成后,使用vs打开sln,生成,在build/src/Debug文件夹内就会出现我们编译出的库文件:glfw3.dll, glfw3.pdb, glfw3dll.exp, glfw3dll.lib

GLAD

  • 因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用,这就是GLAD做的事情。
  • 在线工具设置GLAD相关参数后,下载zip:https://glad.dav1d.de/
  • 使用glad.c和include中的头文件即可,相当于glad库是可以源码引用的。

VS中配置:

编译期:

  • VC++目录中,设置包含目录(指向头文件,用于解析#include命令)
  • 库目录(告诉链接器.lib的位置,用于解决符号引用,并不会决定dll的位置);
  • 附加依赖项需要明确列出需要链接的.lib文件名。
  • 以上这些设置,仅影响代码编译和链接,生成最终的 .exe 或 .dll 文件, 并无法决定exe如何找到dll。

运行时Runtime:

  • 系统如何查找 DLL?运行时,系统会按以下顺序搜索 DLL:
    • .exe 所在目录(推荐方式)。
    • 当前工作目录(可通过调试属性修改)。
    • System32、SysWOW64 等系统目录。
    • PATH 环境变量中的目录。
  • 所以最后,可以将glfw3.dll拷贝至exe位置或者将exe的位置,添加至PATH环境变量。

重要认知(来自deepseek):

  • VC++ 目录中的设置(包含目录、库目录)仅影响编译期(编译和链接阶段),不会影响运行时(执行阶段)。

  • 运行时查找 DLL 的路径与编译期的“库目录”无关,必须通过其他方式(如 PATH 环境变量或 DLL 放置位置

2025-11-12 12:35:09    2    0    0

问题

  • 在brondon换到新的电脑上时,OpenGL和CUDA互操作时注册PBO为cuda资源时老是出错。
  • 代码执行到cudaGraphicsGLRegisterBuffer()函数会出错,CUDA的API接口会抛出一个错误代码999,这表示是未知的错误。
  • 为了测试这个问题,可以使用一个最小的工程测试(通过DeepSeek生成即可),可以发现无论怎么修改都是会出现这个错误的。

原因

参考链接中提到,通过如下链接可以发现显卡被占用,CUDA会抛出一个错误信息,表示没有找到可使用的独立显卡GPU,或者是独立显卡GPU正忙。:

  1. cudaDeviceProp prop;
  2. int dev;
  3. memset( &prop, 0, sizeof( cudaDeviceProp ) );
  4. prop.major = 1;
  5. prop.minor = 0;
  6. HANDLE_ERROR( cudaChooseDevice( &dev, &prop ) );
  7. HANDLE_ERROR( cudaGLSetGLDevice( dev ) );
  1. all CUDA-capable devices are busy or unavailable

但是独立显卡GPU是没有正忙的,是空闲可用的。所以怀疑是调用显卡时出问题,一般有集成显卡,核心显卡和独立显卡,如果系统调用显卡的时候有问题,导致跑CUDA的时候用到了集显或者核显,就会给出错误信息表示找不到可运行CUDA的设备device。
执行以下的代码,系统会列出两块显卡的信息,第一块是英特尔核显,第二块是英伟达独显。

  1. lspci | grep -i vga

然后通过在终端中(命令是"nvidia-settings")打开
Nvidia X Server Settings的界面,
选择PRIME Profiles中的Nvidia(Performance Mode),即设置成独立显卡并重启计算机即可。

2025-11-12 12:34:59    2    0    0

XdmaDriver

  • 简介
    XDMA Driver(Xilinx DMA Driver)是赛灵思(Xilinx)公司提供的一种PCI Express(PCIe)DMA(直接内存访问)驱动程序,主要用于在FPGA(现场可编程门阵列)和主机(PC/服务器)之间实现高速数据传输。它支持Xilinx FPGA上的XDMA IP核,提供用户空间和内核空间的DMA访问能力,适用于高性能计算、数据采集、网络加速等应用场景。
  • 主要功能:
  • PCIe 通信支持
  • DMA直接访问
  • 多进程/多用户支持(高级功能)
  • 用户空间零拷贝优化(如 DPDK 集成)

CVI

  • CVI(LabWindows/CVI)是 National Instruments(NI) 公司推出的一款 ANSI C 集成开发环境(IDE),专为 测试测量、自动化控制和仪器通信 设计。它结合了 C 语言的高效性和图形化编程的便捷性,广泛应用于工业自动化、数据采集、硬件控制等领域。

  • CVI 的核心特点

    • 基于 ANSI C,但扩展了仪器控制功能,支持标准 C 语法,但增加了专门的库(如 VISA、GPIB、DAQmx)用于仪器通信和数据采集。适用于需要高性能、低延迟的嵌入式或实时系统开发。
    • 图形化用户界面(GUI)设计,提供拖拽式 UI 编辑器,可快速创建 面板(Panel)、按钮、图表 等控件,类似 LabVIEW 但基于代码。 适合开发 仪器控制软件、数据监控系统 等交互式应用。
    • 与 NI 硬件深度集成,支持 NI 的数据采集卡(如 PCIe/PXI DAQ)、GPIB 设备、PXI 模块等。可通过 NI-DAQmx、VISA 等驱动直接控制硬件。跨平台兼容性(Windows/Linux),主要运行在 Windows,但部分功能支持 Linux(需 NI Linux Real-Time 系统)。
    • 调试与性能分析工具,内置调试器、内存检查工具,适合开发高可靠性应用。
  • CVI 的典型应用场景

    • 自动化测试系统(如生产线上的仪器控制)
    • 数据采集与信号处理(如传感器数据实时分析)
    • 工业控制与监控(如 PLC 通信、HMI 开发)
    • 科研仪器开发(如光谱仪、示波器控制软件)

OpenGL中

2025-11-12 12:34:47    4    0    0

1、showMaximized()和 showNormal()函数要配套使用

实现全屏最大化以及还原的槽函数on_btnMenu_Max_clicked(),如果不配套使用,比如将showNormal()setGeometry()混合使用,将会出现无法还原的问题。同理,混合使用showMaximized()setGeometry()也会出现问题:

  1. void frmMain::on_btnMenu_Max_clicked()
  2. {
  3. static bool max = false;
  4. static QRect location = this->geometry();
  5. if (max) {
  6. this->setGeometry(location);
  7. //showNormal();
  8. } else {
  9. location = this->geometry();
  10. // 全屏方式一:
  11. // this->setGeometry(QUIHelper::getScreenRect());
  12. //全屏方式二: showMaximized()似乎只能生效一次,第二次就失效了,除非配合上面的showNormal或者下面的hide()就不会有问题
  13. // hide();
  14. showMaximized();
  15. }
  16. this->setProperty("canMove", max);
  17. max = !max;
  18. }

猜测可能是showMaximized()showNormal()内部设置了同一个标志位变量,所以要配套使用,有待看源码证实!

2、Qt中textEdit的总是自动获得焦点

这说明textEdit的焦点策略是strong policy或者tab policy,改成click policy即可。

3、关于enter键

Key_Enter是小键盘的确认键,Key_Return是大键盘的回车键。二者缺一不可。

  1. void Widget::keyPressEvent(QKeyEvent *ev)
  2. {
  3. if(
2025-09-30 10:34:29    5    0    0

(一)单元视图标识符和实例母体标识符

简述:

  • Cell View Identifier(单元视图标识符):定义了一个独立的、完整的设计模块本身。它是“蓝图”或“模板”的ID。
  • Instance Master Identifier(实例母体标识符):定义了一个被引用的、具体的母体对象。它是“根据蓝图制造出来的那个具体零件”的ID。

Cell View Identifier(单元视图标识符)

  • 这指的是一个完整的设计“单元”在库中的唯一身份。它通常由三部分(在某些工具中是两部分)组成:
    • Library: 库名
    • Cell: 单元名
    • View: 视图名
  • 例如: myLib / inverter / schematic,又例如:myLib / inverter / layout
  • 它的作用是:
    • 唯一地标识一个设计文件。当你说“打开 myLib 库下的 inverter 单元的 layout 视图”时,工具就是通过这个标识符来找到并打开那个具体的版图或电路图文件的。
    • 它代表的是一个可编辑的源文件。你可以直接打开、修改并保存它。
  • 一个形象的比喻:- myLib/inverter/layout 就像是一张 “反相器的建筑设计蓝图” 。这张蓝图本身存放在“myLib”这个档案库中。

Instance Master Identifier(实例母体标识符)

这指的是在一个设计中被实例化(引用)的那个目标对象的身份。

当你在一个上层设计(比如 top_level 的版图)中,放置一个子单元(比如 inverter)时,你创建的是一个 “实例”。这个实例本身并不是一个独立的文件,它只是一个指针,指向它所代表的那个底层单元。

这个指针所指向的目标,就是 Instance Master。

它的标识符和 Cell View Identifier 是完全一样的! 也是 Library/Cell/View。

例如:
在你的顶层版图 myLib/top_level/layout 中,你放置了一个反相器。这个反相器实例所指向的 Master 就是 myLib/inverter/layout。

它的作用是:

告诉工具:“我这个实例,是依据哪个蓝

2025-09-30 10:24:23    4    0    0

1、快捷键

1.1 截图:

  • welink截图: ctrl+alt+s截图后, 可以固定在屏幕上;
  • 微信截图: alt+A
  • linux在终端中拷贝和粘贴:
    • 复制:ctrl+insertctrl+shift+C;
    • 粘贴:shift+insertctrl+shift+V;
  • VNC以外粘贴到vncviewer中:
    • 从VNC以外粘贴到vncviewer中去, 需要打开vncconfig才行, 关闭掉vncconfig就不行(难道是bug?), vncconfig路径在/usr/bin下面(可以输入vnc然后tab查看)。
    • 需要从外部粘贴时,在终端中输入vncconfig &, 使其脱离终端后台运行并最小化弹出来的vncconfig设置框, 然后就可以粘贴外部clipboard上的内容了。

1.2 文件:

1.2.1 文件操作

  • 希望每次打开文件夹后, 不是新开一个窗口(窗口太多了, 很乱), 可以这样操作:
    • 随便打开一个文件夹, 选择Edit->Preferences->tab中选择Behavior->勾选Always open in browser windows.
  • 希望每次使用gedit打开文件, 不产生副本(如run.sh, 可能会产生run.sh~这样的副本), 可以这样操作:
    • 随意使用gedit打开一个文件, 选择Edit->Preferences->tab中选择Editor->取消勾选Create a backup copy of files before saving

1.2.2. 文件查找

  • 查找当前路径./dbcmd下某某写的case(递归查找文件,而不是目录名):
    • 直接查找, 可能查找到目录名:find . -name "file.txt"
    • 输出到a.log中去:find ./dbcmd -type f -name "*libo.tar.gz" > a.log
    • 判断数量:find ./dbcmd -type f -name "*libo.tar.gz" | wc -l
  • 仅查找当前文件夹中,文件名包含keyword的文件, 不包含文件夹:
    • find . -type f -name "*ke
2025-07-09 22:24:36    3    0    0

某个源文件多次包含同一个头文件

某个源文件多次包含同一个头文件时,如果头文件中有定义语句,那么不管是什么东西的定义,都可能会产生重定义错误。这种情况是很常见的问题,通常可以使用宏定义保护避免:

  1. #ifndef A_H
  2. #define A_H
  3. /* 这里写真正的头文件 */
  4. #endif

使用#program once也可以,二者区别参考#program once 和 #ifndef

多个源文件包含了同一个头文件

错误原因

但是多个源文件包含同一个头文件,宏定义或者#program once也无法进行避免了,一个本人真实遇到的例子是:。

m_settings.h中:

  1. #ifndef _M_SETTINGS_H
  2. #define _M_SETTINGS_H
  3. ...
  4. namespace setting_ns {
  5. int aa = 10;
  6. QStringList splitArgs(const QString &s, int idx){
  7. // splitArgs函数的定义
  8. }
  9. // 这里因为aa和splitArgs的定义在头文件内,要么改成const和inline,要么将定义放到.cpp中以避免可能的重定义报错
  10. }
  11. #endif

qsetting_obj.h中,包含了m_settings.h:

  1. ···
  2. #include "header/m_settings.h"

很不幸,在main.cpp中,我又同时include包含了qsetting_obj.hm_settings.h,导致变量aasplitArgsmultiple definition of重定义报错,哪怕这里套上命名空间都没用,因为是重复包含导致的和自身重复,而不是和外部的别的符号重复。

按道理来说,使用 #ifdef 等条件编译语句避免头文件在同环境被重复包含,由于#ifndef ...宏定义的保护,只会include一次m_settings.h
但是, 请注意"同环境"这个限定,include虽然只是是简单的将文件复制进cpp吗,当多个cpp源文件包含了同一个头文件时,如果头文件中有某个局部变量或者非内联(inline)函

2023-03-31 18:27:54    63    0    0

1、问题

常见点云存储方式有pcd、ply、txt、bin文件等。kitti数据集的点云是bin格式的,而pcl的数据格式为pcd格式。在使用点云库进行开发时,一般需要将bin格式的点云数据转为pcd格式。网上有很多介绍点云格式如何转换的博客,但是很多的bin2pcd函数的写法都存在小问题,这里记录下我发现的问题并给出解决办法。

2、PointXYZI的内存设计

首先介绍下比较PCL中常见的PointXYZI点类型。
PointXYZI是一个简单的XYZ坐标加intensity的point类型,理想情况下,这4个变量将新建单独一个结构体,并且满足存储对齐,然而由于PCL的大部分操作会把data[4]元素设置成0或1(用于变换),不能让intensity与XYZ在同一个结构体中。因为如果这样的话其内容将会被覆盖。
例如两个点的点积会把他们的第4个元素设置成0, 否则该点积没有意义,等等。因此,对于兼容存储对齐用3个额外的浮点数来填补intensity,这样在存储方面效率较低但是符合存储对齐要求,运行效率较高。

PointXYZI结构体源代码如下:

  1. // PointXYZI占32个字节,8个浮点数的空间,但是只有四个有效,其余的为填充
  2. struct PointXYZI {
  3. union {
  4. float data[4];
  5. struct {
  6. float x;
  7. float y;
  8. float z;
  9. }
  10. }
  11. union {
  12. struct {
  13. float intensity;
  14. }
  15. float data_c[4];
  16. }
  17. }

3、问题写法一

3.1 问题发现

方法一生成的pcd文件中,可以正常可视化,但是由于最后一个点存在x=0,y=0,z=0,intensity=0的情况,生成深度图时,会出现崩溃问题。

对于使用ascll编码的pcd文件是可以用记事本打开的, 我们打开导致崩溃的pcd文件,可以发现最后一个点是0 0 0 0,这将导致创建深度图时

2022-12-02 15:43:57    50    0    0

1、左值右值

1.1 左值

  • 放在等式左边,可以取地址并且有名字的变量。比如:
    • 函数名和变量名
    • 返回左值引用的函数调用
    • 前置自增减表达式++i、--i
    • 由赋值表达式或赋值运算符连接的表达式(a=b,a+=b等)
    • 解引用表达式*p
    • 字符串字面值"abcd"
      • 因为字符串字面值具有固定存储:其在程序的整个生命周期中都存在(通常存储在只读数据段)
      • 可以取地址:&"abcd" 是合法的(虽然实际中很少这样用)
      • 可以绑定到左值引用
  1. int a= 4; //a是左值,4 作为普通字面量是右值

1.2 右值

  • 不能取地址的没有名字的东西就是右值,一般放在等式右边。C++11以后,右值又分为纯右值和将亡值。

1.2.1 纯右值

  • 运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。例如:
    • 除字符串字面值外的字面值,包括整数,浮点数,字符,布尔值,nullptr等;
    • 返回非引用类型的函数调用
    • 后置自增减表达式i++、i--
    • 算术表达式(a+b,a*b, a&&b,a==b等)
    • 取地址表达式等(&a)

1.2.2 将亡值

  • 将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值。将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间的方式获取的值,在确保其它变量不再被使用或即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。
  1. 1 class A {
  2. 2 xxx;
  3. 3 };
  4. 4 A a;
  5. 5 auto c = std::move(a); // std::move(a)是将亡值
  6. 6 auto d = static_cast<A&&>(a); // static_cast<A&&>(a)是将亡值

3、左(右)值引用

左值引用就是对左值进行引用的类型,右值引用就是对右值进行引用的类型。都是别名,必须立即初始化。

  1. type &name = exp; // 左值引用
  2. type &&name = exp;
2022-12-01 17:50:45    38    0    0

问题

反射,即Reflection,是很多高级编程语言的一种十分强大的特性,比如Java,可以于运行时加载、探知、使用编译期间完全未知的classes。而C++是天然不支持反射的,但是可以通过一些特殊手段实现类似反射的效果,本文对使用字符串创建对象,稍作探讨。

目标

我们要实现的目标是要通过字符串创建对象,即下面的代码:

  1. ClassT* obj = FactoryCreate("ClassT");

但是我们要知道,这并不是完全意义上的反射,因为不能取到对象的属性和值,所以只是伪反射。

实现

  • 基本思路就是,声明要继承的基类BaseClass,BaseClass就是给用户继承使用类,并且其可以声明为任意类型,并通过宏定义REGISTER进行工厂模式注册构造函数。
  • 工厂Factory中通过map关联字符串和构造函数。最后,我们就可以通过类名字符串,在map中找到相应的构造函数,从而获取到(或者构建)字符串对应的用户自定义类的对象。
  1. //
  2. // Created by libo on 2022/11/30.
  3. //
  4. #include <map>
  5. #include <string>
  6. #include <iostream>
  7. #define REGISTER(className) \
  8. className* objectCreator##className(){ \
  9. return new (className); \
  10. } \
  11. RegisterAction g_creatorRegister##className( \
  12. #className, (constrFuncPtr)objectCreator##c