- 博客(608)
- 资源 (22)
- 收藏
- 关注
原创 Vulkan进阶系列8 - 绘制(完整代码)
在 Vulkan 中,绘制流程 涉及多个重要概念,比如 framebuffer、交换链(swapchain)、命令缓冲(command buffer) 等;Vulkan 的绘制大致流程是:创建 交换链(Swapchain),用于呈现图像到屏幕;为每个交换链图像创建 image view;创建 framebuffer(帧缓冲)用于渲染目标;使用 command buffer 记录渲染命令;将渲染结果输出到 framebuffer(也就是 swapchain image);呈现到窗口。
2025-04-06 16:08:23
5
原创 Vulkan进阶系列7 - 创建图形管线(完整代码)
我们可以将图形管线看作 GPU 的一条“生产线”,图形数据(比如顶点、纹理)会经过多个阶段的加工,最后输出成图像(写入 framebuffer)。Vulkan 将图形管线设计成一个高度显式、可控的 GPU 渲染流程模型,从顶点处理一直到颜色混合,每一步都需要我们来明确配置。这就是为什么要在vulkan中创建图形管线的原因。
2025-04-05 20:26:17
17
原创 Vulkan进阶系列6 - 创建交换链(完整代码)
Vulkan 的设计目标是:最大程度减少驱动对开发者行为的“猜测和干预”,把一切都交给开发者来决定。所以Vulkan没有默认帧缓冲区,需要我们自己创建交换链(swapchain);Vulkan 中的 swapchain 是一组图像缓冲区(images),这些图像按顺序被绘制并最终显示到屏幕上。当我们使用 Vulkan 渲染时,不是直接画到屏幕上,而是画到这些 swapchain image 上,然后再“交换”到前台去显示。
2025-04-05 16:14:16
101
原创 Vulkan进阶系列5 - 创建逻辑设备(完整代码)
逻辑设备(Logical Device)是 Vulkan 中抽象出来的一层,用于访问 GPU 的功能。物理设备(Physical Device)代表实际的 GPU 硬件本身,而逻辑设备是Vulkan程序与该硬件打交道的“接口”。创建逻辑设备后,我们才能:向 GPU 提交绘图命令;访问图形 / 计算队列;使用 GPU 支持的特性(如 samplerAnisotropy、geometry shader 等);创建 buffer、image 等资源;为什么不直接用物理设备,而要创建一个逻辑设备呢?
2025-04-05 06:50:35
11
原创 Vulkan进阶系列4 - 选择物理设备(完整代码)
Vulkan 中的“物理设备”就是系统中的显卡或其他支持 Vulkan 的计算设备。我们通常会选出最合适的一个作为渲染设备。选择设备调用流程如下:获取所有可用物理设备打印设备属性(可选)判断设备是否“适合”检查设备是否支持所需扩展选中第一个符合条件的设备,保存到。
2025-04-04 20:46:59
17
原创 Vulkan进阶系列3 - 添加Validatioin Layers(完整代码)
Validation Layer(验证层)是 Vulkan 提供的一种 调试和错误检测机制,主要用于在 开发阶段 发现 API 使用错误,增强 Vulkan 代码的稳定性和可靠性。这些层可以拦截 Vulkan API 调用,并检查参数的合法性、对象的正确使用以及其他潜在的问题。
2025-04-04 13:49:55
11
原创 Vulkan进阶系列2 - Vulkan初始化(完整代码)
Vulkan初始化过程是:查询Vulkan版本->创建Vulkan实例->管理Vulkan扩展。下面介绍下初始化用到API及其作用。vkEnumerateInstanceVersion 是 Vulkan 1.1 引入的一个函数,它用于查询 Vulkan 运行环境支持的最高实例级(instance-level)API 版本。它的主要作用是 在创建VkInstance之前,获取 Vulkan 驱动程序支持的最高 API 版本,方便应用程序做兼容性检查。参数pApiVersion 是。
2025-04-04 09:25:48
117
原创 Vulkan进阶系列1 - Vulkan应用程序结构(完整代码)
在前面的20多篇文章中,我们了解了Vulkan的基础知识,和相关API的使用,接下来我们要从零开始写一套完整Vulkan应用程序,在这个过程中加深对Vulkan中的各种概念的理解。Vulkan 应用程序一般遵循 初始化 -> 运行循环 -> 资源清理 的结构,本实例也基本遵循了这一模式。对于一个Vulkan 图形应用程序来说,首先有一个窗口,其次是要处理窗口事件,应用程序逻辑状态,最后是图形的绘制。本文搭建一个简单应用程序框架,目标是将来在这样的框架下,能跑起一系列Vulkan图形应用程序。这个。
2025-04-03 21:22:19
369
原创 C++ 编程指南33 - 使用模板来表达适用于多种参数类型的算法
在 C++ 中,模板(Templates)提供了一种强大的泛型编程方式,使代码可以适用于不同的数据类型,而无需重复编写类似的逻辑。模板的主要目标是:泛化能力(Generality):能够适用于不同的数据类型,提高代码的通用性。减少源代码量(Minimizing the amount of source code):避免为不同数据类型重复编写代码,提高代码复用性。互操作性(Interoperability):使代码能够在不同类型的容器或数据结构之间共享。
2025-04-02 22:44:24
262
原创 OpenGL进阶系列20 - OpenGL SuperBible - bindlesstex 例子学习
什么是无绑定纹理(bindless texture)?无绑定纹理是OpenGL的一项技术,旨在消除传统的纹理绑定操作。这项技术允许着色器直接访问纹理而不需要显式地将纹理绑定到某个纹理单元,从而减少了渲染管线中的开销。在引入无绑定纹理之前,当我们想在着色器中使用纹理时,我们会将其绑定到一个纹理单元,并通过一个样本类型的统一变量(例如sampler2D)表示该纹理单元。这个样本变量与某个纹理单元相关联,而这种关联形成了对底层纹理的间接引用。
2025-04-02 20:35:11
239
原创 C++编程指南32 - 模板编程时要避免过度约束以提高通用性
由于使用模板会提高代码的抽象级别,所以在编写模板代码时,要尽量让代码更灵活、通用,而不是只针对某些特定的操作或类型。目的是为了提高代码的重用性和效率。不要过度限制模板的类型要求,让代码更通用。尽量避免只要求单一操作,要考虑更通用的操作集合。用概念来约束类型,而不是直接依赖操作符,这样代码能处理更多类型,增加灵活性和复用性。
2025-04-01 21:15:41
453
原创 Linux 中dmesg, kern.log, syslog 什么关系?有什么用?
dmesg 用于查看内核日志缓冲区中的信息,主要是内核启动过程,驱动加载,设备检测等日志,但不会将日志保存到磁盘。kern.log 用于存储内核相关日志,从demsg 中获取内核日志,并写入/var/log/kern.log。syslog 是一个通用的日志文件,布局包括内核日志,还包括用户空间应用程序、网络、认证等各种日志,存储在/var/log/syslog中。
2025-04-01 15:31:54
245
原创 C++编程指南31 - 除非绝对必要,否则不要使用无锁编程
无锁编程容易出错,并且需要专家级的知识,包括:语言特性(如 C++ 的计算机架构(如 CPU 缓存一致性协议)数据结构(如无锁队列、无锁栈)高层次的并发机制(如线程和互斥锁)本质上是基于无锁编程实现的,但它们隐藏了底层的复杂性,使开发者更容易正确使用。如果必须使用无锁数据结构,优先使用现成的库(Facebook 开源库)实现了无锁链表libcds提供成熟的无锁数据结构(队列、哈希表等)
2025-03-31 20:57:30
264
原创 Ubuntu24.04 配置远程桌面服务
gsettings set org.gnome.Vino require-encryption false # 关闭加密(某些 VNC 客户端不支持加密)gsettings set org.gnome.Vino prompt-enabled false # 关闭连接请求提示。三:开启服务(默认端口5900)
2025-03-29 21:18:57
400
原创 驱动开发系列49 - 搭建 Vulkan 驱动调试环境(编译 mesa 3D)- Ubuntu24.04
10. 对于intel驱动,如想调试 vkCreateInstance 函数,则到Mesa库中找 anv_CreateInstance 方法,设置断点。2. cd mesa-24.2.8 #比如下载的是mesa-24.2.8。1. apt source mesa #下载mesa代码。
2025-03-29 08:48:43
290
原创 C++编程指南30 - 不要为初始化编写自己的双重检查锁定
自 C++11 起,静态局部变量的初始化已经是线程安全的。结合 RAII(资源获取即初始化)模式,静态局部变量可以完全取代双重检查锁定的需求。此外,也可以实现相同的目的。因此,应该使用 C++11 的静态局部变量或,而不是自己编写双重检查锁定。不要 手动实现双重检查锁定(DCLP, Double-Checked Locking Pattern)。进行一次性初始化,适用于非局部的初始化情况。C++11 的线程安全静态局部变量,适用于局部作用域的初始化。
2025-03-25 23:21:07
421
原创 C++编程指南29 - 仅在与非 C++ 代码交互时使用 volatile
volatile关键字用于声明与**“非 C++”代码或不遵循 C++ 内存模型的硬件交互的对象。例如,它常用于表示硬件寄存器**,因为这些寄存器的值可能在程序未直接操作的情况下发生变化。
2025-03-25 22:54:13
409
原创 C++编程指南28 - 使用 std::async() 启动并发任务
与(之前介绍的)避免使用裸指针管理资源类似,我们应该避免直接使用std::thread和std::promise,而是使用std::async 这样的工厂函数来启动并发任务。std::async能够自动决定是创建新线程,还是重用已有的线程,从而避免直接管理std::thread带来的复杂性和潜在错误。
2025-03-13 22:35:17
177
原创 驱动开发系列48 - Linux 显卡KMD驱动代码分析(九)- 送显过程(扫盲)
在 Linux 图形栈中,从 Framebuffer(帧缓冲)到最终显示在屏幕上的过程涉及多个关键组件。这些组件共同协作,负责图像的存储、处理、合成以及输出,确保图形数据能够正确渲染到显示器上。Framebuffer 是 GPU 内存中的一块区域,存储了像素数据(RGB、YUV等),本质可以理解为一张“图片”,最终会被显示到屏幕上;由用户空间程序(如 Xorg、Wayland)分配创建。在内核中由 drm_framebuffer 结构体管理。
2025-03-13 18:32:24
511
原创 C++ 编程指南27 - 始终将 mutex 与它所保护的数据一起定义,并尽可能使用 synchronized_value<T>
在多线程编程中,互斥锁(std::mutex)的作用是保护共享数据的访问。但如果mutex锁的使用不明显:程序员可能会忘记获取mutex就访问数据,导致数据竞争(race condition)。锁管理混乱:代码阅读者难以明确哪个mutex保护哪个数据,可能会误用错误的mutex,导致死锁或数据不一致。封装性不足:数据和mutex分离,使得访问数据变得不安全。
2025-03-11 23:57:23
298
原创 驱动开发系列47 - Linux 显卡KMD驱动代码分析(八)- 使用bpftrace 来调试内核驱动
经过前面七节的学习,我们对显卡 KMD(Kernel Mode Driver)驱动的实现有了初步认识,其中未涉及 GPU 内部实现细节(因这些内容通常由 GPU 厂商保密)。接下来的章节将继续深入学习 KMD 显卡驱动的实现。在此之前,先介绍一种强大的显卡内核驱动调试工具——bpftrace,以便更好地了解显卡的内部运行机制。bpftrace是基于 eBPF(Extended Berkeley Packet Filter) 的高级内核跟踪工具,用于分析和调试 Linux 内核及应用程序的行为。
2025-03-11 23:47:12
218
原创 C++ 编程指南26 - 尽量缩短在临界区(critical section)内的执行时间
在临界区中持有互斥锁(mutex)的时间越短,线程之间的等待时间就越少,从而减少线程被挂起和恢复的开销,提高程序的并发性能。一般来说,无法自动检测是否持有mutex过长。但可以标记裸露的lock()和unlock()调用,鼓励使用 RAII 进行资源管理。或者进行代码审查(Code Review) 确保临界区最小化。
2025-03-10 23:58:38
258
原创 C++编程指南25 - 不要在没有条件的情况下等待
如果没有条件就进行等待(wait()),可能会导致以下问题:一是某个线程可能会错过其他线程的通知,从而一直处于等待状态。二是线程可能被唤醒后发现根本没有可执行的任务,导致不必要的上下文切换和资源消耗。永远不要 在没有条件的情况下调用wait(),否则可能会导致线程永远等待或不必要的唤醒。始终 使用条件谓词(如!q.empty())来控制wait(),确保线程只有在满足特定条件时才会被唤醒。检查代码,确保所有wait()调用都使用了谓词表达式,否则应该标记为潜在错误。
2025-03-10 23:53:45
368
原创 驱动开发系列46 - Linux 显卡KMD驱动代码分析(七)- 显存管理
显存管理是图形驱动程序中至关重要的一部分,涉及到从用户空间(UMD,User Mode Driver)到内核空间(KMD,Kernel Mode Driver)的显存分配和管理。本文将首先梳理从一个 OpenGL 应用程序到 UMD,再到 KMD 的显存分配过程,然后介绍 KMD 在显存管理方面的角色和工作原理。
2025-03-09 09:07:12
154
原创 C++编程指南24 - 避免线程频繁的创建和销毁
线程的创建和销毁是昂贵的操作,尤其在多线程程序中频繁创建和销毁线程时,可能会导致性能问题。特别是当消息处理需要创建多个线程时,系统资源会被过度消耗。在高并发处理的场景中,线程池是非常常见的解决方案。
2025-03-08 21:49:58
271
原创 驱动开发系列45 - Linux 显卡KMD驱动代码分析(六)- 显卡驱动与OS接口
在显卡驱动中,为了实现与操作系统内核功能的解耦和抽象,通常会采用一套专门的接口层。显卡驱动所有其他模块在调用与内核交互的底层功能时,都通过这个接口进行调用,而不用直接依赖具体的内核实现。本文介绍下一般驱动程序都需要哪些操作系统的接口。延时与计时接口在操作系统中,精确的延时和计时是保证硬件操作同步的重要手段。接口层中提供了诸如mdelayudelaymsleep等延时函数,它们分别实现毫秒级和微秒级的延时操作,而计时函数则利用 CPU 的时间戳计数器(如和rdtscll)来获取高精度的时间基准。
2025-03-08 18:41:01
274
原创 驱动开发系列44 - Linux 显卡KMD驱动代码分析(五)- GPU和CPU的任务同步
在 GPU 的执行过程中,GPU 与 CPU 之间的同步非常关键,特别是在异步执行的场景中,GPU 需要完成某些任务(如渲染、计算)后,CPU 才能接收结果或进行后续操作。Linux 提供了一种Fence用于实现 GPU 与 CPU 或 GPU 之间同步的机制,特别适用于异步执行环境。
2025-03-06 23:17:17
184
原创 C++编程指南23 - 在无关线程之间共享资源时应使用shared_ptr
当多个线程需要共享一个堆内存(即动态分配的内存)时,如果这些线程之间没有直接关系,比如它们的生命周期不重叠,那么使用shared_ptr(智能指针)是最安全的做法。因为如果有多个线程要共享一块内存,而且这块内存需要在某个时候被删除,直接用普通指针可能会导致一些问题,比如内存泄漏或者在某个线程结束时,其他线程仍然在访问这块已经被删除的内存。shared_ptr是一种智能指针,它会自动管理内存的释放。只有当所有的线程都不再使用这块内存时,shared_ptr才会销毁它,避免了内存泄漏和重复释放的问题。
2025-03-06 23:02:13
299
原创 驱动开发系列43 - Linux 显卡KMD驱动代码分析(四)- DRM设备操作
DRM(Direct Rendering Manager)是Linux内核中的一个子系统,主要负责图形硬件的管理与图形渲染的加速。它为图形驱动提供了一个统一的接口,可以使用户空间程序与图形硬件进行直接交互,而无需通过X服务器或Wayland等显示管理器。DRM不仅用于管理显卡,还处理视频输出、显示缓冲区管理和硬件加速渲染等任务。在Linux内核中,所有与图形硬件相关的操作都是通过DRM来完成的,这包括显卡的初始化、驱动加载、上下文切换、内存分配、缓冲区管理等功能。
2025-03-05 23:19:18
382
原创 C++编程指南22 - 在线程之间传递少量数据时,使用值传递,而不是引用或指针传递
传递少量数据时,复制比通过某些锁机制共享数据更便宜。复制数据自然会导致唯一所有权(简化代码),并消除了数据竞争的可能性。注意:“少量数据”的定义是无法精确界定的。这个规则的核心思想是在线程间传递少量数据时,尽量使用值传递,而非引用或指针传递。避免数据竞争通过值传递,数据拥有独立的副本,不需要担心多线程间对同一内存位置的访问冲突。这避免了数据竞争(data race),简化了代码。相比之下,如果使用引用或指针传递,多个线程可能会同时访问和修改同一数据,导致难以调试的并发问题。复制的开销较低。
2025-03-05 23:09:54
426
原创 驱动开发系列42 - Linux 显卡KMD驱动代码分析(三) - 中断注册和读写PCI设备配置信息
中断是一种计算机硬件机制,允许设备(GPU)向CPU发出信号,表示它需要CPU的注意。中断可以提高系统相应速度,避免CPU轮询等待设备状态。在Linux中将中断抽象为IRQ(中断硬件请求),用中断号(IRQ Number)标识每个中断源。当设备发送中断时,CPU进入中断上下文,暂停当前任务,执行中断处理程序,完成设备状态处理。返回原任务或调度新任务。
2025-03-04 22:32:43
282
原创 LLVM - 编译器前端 - 学习将源文件转换为抽象语法树(三)
有三种不同类别的标记(token):关键字(keywords)、标点符号(punctuators)和通用标记(tokens),它们分别表示多个值的集合。每个标记在枚举(enumeration)中都需要一个对应的成员名称。关键字和标点符号具有自然的显示名称,可用于消息输出。类型分配唯一的编号。将所有内容都放在一个头文件和实现文件中是不具备可扩展性的,因此我们需要对其进行拆分。正如我们在上一章中所了解到的,我们需要一个。组件,但被放置在不同的头文件和实现文件中。可以被通用地使用,因此被放置在。
2025-03-04 06:21:41
52
原创 LLM - Attention Is All You Need 的理解
当前主流的序列转换(sequence transduction)模型主要基于复杂的循环神经网络(Recurrent Neural Networks, RNNs)或卷积神经网络(Convolutional Neural Networks, CNNs),这些模型通常包含编码器(encoder)和解码器(decoder)。性能最优的模型通常通过“ 注意力机制(attention mechanism)”将编码器和解码器连接起来。
2025-03-03 23:21:38
185
原创 驱动开发系列41 - Linux 显卡KMD驱动代码分析(二) - 设备电源管理
Linux的电源管理(Power Management),主要用于优化设备功耗,在保证系统稳定性的同时,降低能耗,提升续航。本文将介绍下显卡设备的电源管理。Linux的电源管理主要包含系统电源管理(System Power Management)和运行时电源管理(Runtime Power Management)两个部分。系统电源管理控制整个系统的电源状态,影响所有设备的运行,典型状态有S3(Suspend-to-RAM),S4(Hibernate);
2025-03-03 21:12:50
377
原创 LLVM - 编译器前端 - 学习将源文件转换为抽象语法树(二)
一种简单的方法是,每条消息都有一个 ID(一个枚举成员)、一个 严重级别(如 错误(Error) 或 警告(Warning)),以及一个包含具体消息的 字符串。在代码中,你只需要引用消息 ID,而严重级别和消息字符串仅在打印消息时使用。在一个庞大的软件(比如编译器)中,我们不希望将消息字符串分散在各个地方。如果需要修改消息内容或将其翻译成另一种语言,最好将它们集中存放在一个地方!目前缺少的是对消息的集中定义。下面我们看看来如何实现它。数据存储在 .def 后缀的文件中,并用 宏 进行包装。
2025-03-03 06:17:39
533
原创 C++编程指南21 - 线程detach后其注意变量的生命周期
如果一个线程被detach()了,那么它的生命周期将独立于创建它的作用域。全局变量(global/static objects)堆上分配的对象(free-store allocated objects,即new创建的对象) 其他作用域的对象可能会在线程访问它们之前被销毁,导致 悬空指针(dangling pointer) 和 未定义行为。
2025-03-02 22:07:25
275
原创 C++编程指南20 - 使用 joining_thread以确保线程不会在变量生命周期之外运行
在多线程编程中,如果一个线程需要访问外部作用域的变量,就必须保证这个变量在 线程结束之前依然有效,否则可能会出现 悬空指针(dangling pointer) 的问题,导致 未定义行为。
2025-03-02 21:50:36
231
原创 C++编程指南19 - 在持有锁的情况下,绝不要调用未知代码(例如回调函数)
在持有互斥锁的情况下调用 未知代码(例如回调函数或虚函数) 可能会导致 死锁(deadlock) 或 长时间阻塞(blocking),从而影响程序的性能和正确性。
2025-03-02 20:40:24
276
OpenGL Programming Guide (Red Book) 9th Edition Source Code
2024-10-07
Learn LLVM 17 A beginners guide to learnin - Kai Nacke.pdf
2024-07-08
Power and Performance Software Analysis and Optimization pdf
2024-04-09
计算机图形学经典书籍资料-渲染部分
2014-11-06
计算机图形学经典书籍资料-建模部分
2014-11-06
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人