游戏引擎学习第244天: 完成异步纹理下载
启动并运行游戏,注意到我们的纹理没有被下载
我们将继续完成游戏的开发。昨天,我们已经实现了多线程的纹理下载,但并没有时间调试它,因此纹理下载功能目前并没有正常工作。我们只是写了相关的代码,但由于大部分时间都花在了讨论这些功能和在黑板上描述它们上。因为我想尽快完成,所以加班加点做了所有的工作。
接下来,我将启动并加载我们当前的代码,并进行构建和运行。你可以看到,目前程序并没有崩溃,但纹理没有被正确下载。具体来说,纹理并没有成功传输到显卡上,导致游戏运行时只显示了一个白色的纹理,没有任何内容。
从一些关于从零开始编写软件的思考开始,偶尔需要使用一些黑盒系统
今天的工作内容是调试图形驱动的问题,这通常是一项非常艰巨的任务。调试图形驱动就像是在Windows系统上调试一样,最大的困难在于你无法深入到驱动程序的代码中去查看发生了什么,因此只能把它当作一个“黑盒”来处理。虽然偶尔可以从这个“黑盒”中得到一些错误信息,帮助我们进行调试,但总的来说,调试图形驱动要比调试我们自己的代码难得多。因为我们可以完全了解自己代码的运行过程,而使用图形硬件加速后,就失去了这种控制。
使用图形硬件加速的好处显而易见,它能大幅提升游戏性能,尤其是当我们希望游戏能够与其他商用游戏相比时。但这个过程中也有代价,图形硬件加速引入了复杂的驱动程序和图形卡特定的内容,这使得我们无法像调试自己的代码一样,随时查看每个细节。一旦使用了GPU,就有了大量的驱动程序代码介入,而这部分内容我们无法深入查看。这样一来,调试过程中就失去了控制,给我们带来了极大的挑战。
我们之前在开发过程中很享受能够直接查看汇编代码并了解每一行指令做了什么的情况,但一旦使用了硬件加速,我们就不再能拥有这种精确控制的能力。未来的理想是,能够拥有一种处理器,所有的图形处理任务都由同一个处理器处理,而我们可以对其进行全面控制,不再依赖外部的显卡和驱动程序。那时,我们将能够完全控制渲染过程,享受和软件渲染一样的自由度。
然而,现实并非如此,我们仍然需要依赖图形卡和驱动程序,这就是我们当前所面临的现实。因此,我们尽量减少与驱动程序的直接交互,将图形系统与其他系统隔离开,以减少这个黑盒带来的困扰。这种隔离让我们能够更好地应对调试过程中遇到的问题,但最终还是无法完全避免依赖黑盒系统的情况。
在当前的调试过程中,我们只能有限地检查是否正确创建了OpenGL上下文等基本操作,除此之外,其他部分的调试将会受到很大限制。因此,我们需要使用一些针对“黑盒”调试的技巧,这就像我们在调试Windows平台层时无法看到具体实现一样,或者像在使用高级语言(例如Python、Java等)时,整个系统都在我们上方,我们无法直接查看底层与硬件的交互过程。
调试器:浏览我们新的OpenGL代码
今天的任务是通过检查新的OpenGL代码,找出任何明显的错误,因为我们可能遇到了一些简单而显而易见的bug。首先要做的是确认我们所做的更改仍然符合预期,确保没有做错什么。
在代码中,我们正好进入了初始化OpenGL上下文的部分。一个值得注意的点是,初始化OpenGL上下文的过程本身就已经有明显的延迟。当我们试图初始化硬件加速时,启动时间显著变长。以往,启动过程几乎是即时的,但现在,只是初始化OpenGL上下文就让启动时间变慢了十倍,这让人很沮丧。显然,要和硬件交互,我们必须通过驱动程序层,而这层是无法绕过的,也无法优化掉这种延迟。
在初始化完OpenGL上下文后,程序正确地创建了上下文并设置为当前状态。这部分的代码与之前一样,运行正常。接下来,我们将OpenGL的上下文保存在一个全局变量中,确保在后续过程中能够使用这个上下文。
接下来的步骤是获取窗口的设备上下文(DC)。我们用的是CS_OWNDC
窗口类,这意味着系统会管理并且不需要我们手动释放它。然而,我对这一点有所怀疑,虽然理论上它不需要释放,但为了确保万无一失,我决定再确认一下,查看是否确实不需要手动管理这个设备上下文。最终,我决定继续保持当前实现,避免改变CS_OWNDC
为其他类型,以减少潜在问题的发生。
总的来说,虽然在与硬件交互时有些不可控的问题,调试和修改过程中,我们仍然在尽量控制问题的范围,并确保每一步都经过仔细检查。
win32_game.cpp:让Win32InitOpenGL返回一个HGLRC
为了改进代码,我决定对全局设备上下文(DC)进行一些调整,以避免过度依赖全局变量并提高代码的整洁性。首先,我计划修改OpenGL初始化的部分,让它返回OpenGL上下文(RC),而不是将其赋值给全局变量。这是为了让代码更加灵活,不那么依赖于全局变量,并且避免将资源的管理过度集中在一个地方。
接下来,我注意到在初始化OpenGL时,我们传入了一个窗口参数,但实际上我们并没有使用这个窗口。因此,我决定调整代码,使用更合适的方式来处理这个参数。我计划将窗口传递给一个平台层的对象,这样可以使代码结构更加清晰,并避免不必要的全局变量。
我认识到平台层的代码通常会比较复杂,有时不得不使用一些全局变量或处理方式。尽管如此,我认为仍然有机会使代码更简洁、可读,从而减少未来潜在的错误和混乱。因此,我决定让代码看起来更简洁,并确保它更加可维护。
我决定对代码做这些修改后,将设备上下文的管理从全局变量中分离出来。在新的实现中,首先获取设备上下文,然后在使用完毕后释放它,这样可以避免不必要的全局依赖,同时使代码更具可控性。
最终,我对这些修改进行编译,查看是否有错误,并确保修改后的代码没有问题。通过这种方式,平台层的代码变得更加清晰,也减少了对全局状态的依赖,使得代码结构更加符合我们预期的设计。
调试器:进入Win32InitOpenGL并考虑在队列外创建OpenGL上下文
在这里,我们进入了OpenGL初始化的部分。我们发现之前的延迟问题,特别是在初始化OpenGL上下文时,出现了明显的延迟。这个延迟是由于硬件加速初始化的过程,这个过程本来就需要一定的时间,而且随着复杂性的增加,这个过程会变得更加显著。
为了改进这一部分,我决定对代码做一些调整。首先,我们计划返回OpenGL上下文,而不再直接将其存储为全局变量。这样做的目的是让代码更加灵活,同时避免过度依赖全局变量,确保资源管理更加明确。
接下来,创建OpenGL上下文的操作依赖于任务队列的执行,但在创建线程时,我们需要确保线程创建完成并开始执行,否则可能会发生竞争条件,导致在其他线程初始化OpenGL之前,窗口设备上下文(DC)被释放掉。这就需要特别小心,确保在释放之前,OpenGL上下文已经成功创建。
有两种方式可以解决这个问题:一种是创建OpenGL上下文的操作放在任务队列之外,这样可以避免释放DC时发生的竞争问题;另一种方法是确保在释放DC之前,所有的线程操作都已经完成。这就要求我们仔细控制线程的执行顺序,以防止发生资源访问冲突。
总体来说,这部分的调整旨在确保多线程环境下的资源管理更加稳定,避免因为线程竞态导致的潜在问题。同时,改进代码结构,使得上下文管理更加清晰,减少全局变量的使用,从而提高代码的可维护性。
win32_game.cpp:将窗口声明为CS_OWNDC
我们最终确认,不能在初始化OpenGL之后立刻释放设备上下文(DC),因为多线程操作中存在竞争条件。线程在启动之后可能会在我们释放DC之前才尝试使用它来创建OpenGL上下文,如果我们提前释放,就可能导致上下文创建失败,进而引发渲染错误或程序崩溃。
因此,我们决定保留这个DC的引用,不在初始化后立即释放它。为了安全起见,也为了遵循Windows平台上更规范的资源管理方式,我们将窗口设置为 CS_OWNDC 类型的窗口。这样系统就允许我们持有设备上下文不释放,也不会产生资源冲突的问题。使用CS_OWNDC可以确保每个窗口都拥有一个专属的设备上下文,并且在整个窗口生命周期内都可以安全持有和使用它。
虽然这是一个临时解决方案,但目前来看这是最直接、最安全的方式,可以让我们在不引发线程争用问题的前提下,稳定地完成OpenGL上下文的创建和绑定。未来如果觉得这种方式不够优雅或灵活,也可以再做进一步优化,比如提前完成上下文创建之后再启动线程,或者将创建和渲染职责完全解耦,但现在我们先采用这个做法,以保证系统稳定运行。
接下来我们继续调试和观察系统行为,确认这部分逻辑已经如预期那样安全地完成初始化,并确保不会引发后续问题。
窗口指定 OWNDC
和 ReleaseDC
之间的关系,其实围绕的是 设备上下文(Device Context, DC) 的获取与生命周期管理方式。我们来详细解释一下它们的区别和关系:
🔍 什么是 OWNDC
在 Win32 API 中,创建窗口时使用的 WNDCLASS
或 WNDCLASSEX
结构体中,有一个 style
字段。这个字段可以设置一些窗口类的行为标志。其中:
CS_OWNDC
表示:
为该窗口分配一个私有的、唯一的设备上下文(DC),并在窗口创建时创建它,窗口销毁时释放它。
CS_OWNDC
的行为特点
- 每个窗口都有一个独立的 DC,且不需要每次绘制时调用
GetDC
和ReleaseDC
。 - 这个 DC 会在窗口创建时分配,并一直存在于窗口生命周期内。
- 适合用于需要频繁绘图的场景(比如使用 OpenGL 的窗口)。
GetDC
/ ReleaseDC
如果 没有设置 CS_OWNDC
,那你就需要在每次使用设备上下文时:
HDC hdc = GetDC(hwnd);
// 使用 hdc 绘图
ReleaseDC(hwnd, hdc);
这是因为:
- Windows 会从一个 DC 池中临时分配一个 DC 给你。
- 如果你不及时调用
ReleaseDC
,那这个 DC 会被占用,资源会泄露。 - 多次
GetDC
调用之间拿到的可能不是同一个 DC(除非系统复用了同一份)。
它们之间的关系总结
行为 | CS_OWNDC 有效时 | 普通窗口(无 OWNDC ) |
---|---|---|
DC 分配 | 窗口创建时分配一次 | 每次调用 GetDC 时临时分配 |
DC 生命周期 | 窗口销毁时释放 | 必须手动调用 ReleaseDC |
是否需要 ReleaseDC | ❌ 不需要 | ✅ 必须调用 |
是否线程安全 | ✅ 是 | ⚠️ 否,DC 池是共享的 |
推荐场景 | OpenGL / DirectX / 高频渲染 | 普通界面绘图 / WM_PAINT |
注意事项
- 使用
CS_OWNDC
的窗口,内存使用会略多,因为每个窗口有独立 DC。 - 不要在
OWNDC
窗口上调用ReleaseDC
,否则行为未定义,甚至可能崩溃。 - 用于 OpenGL 的窗口强烈推荐使用
CS_OWNDC
,因为 OpenGL 需要一个长期存在的 DC 来与 RC(Rendering Context)绑定。
示例:如何设置 OWNDC
WNDCLASS wc = {};
wc.style = CS_OWNDC;
wc.lpfnWndProc = MyWindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"MyWindowClass";
RegisterClass(&wc);
这样注册的窗口类,创建出的窗口就会有自己专属的 DC。
示例:使用 RC 时的逻辑
// 创建窗口(CS_OWNDC)
HWND hwnd = CreateWindow(...);// 获取 HDC,一次即可
HDC hdc = GetDC(hwnd);// 创建 OpenGL RC
HGLRC rc = wglCreateContext(hdc);
wglMakeCurrent(hdc, rc);// ... OpenGL 渲染逻辑 ...// 窗口销毁前
wglMakeCurrent(NULL, NULL);
wglDeleteContext(rc);
ReleaseDC(hwnd, hdc); // 如果用了 OWNDC,其实可以不 ReleaseDC
调试器:进入Win32CreateOpenGLContextForWorkerThread
我们现在需要为某个工作线程创建一个 OpenGL 上下文。这个线程应该是标记了 needs_opengl
的,也就是说,它是属于低优先级的任务队列之一。
接下来我们定位到创建工作线程 OpenGL 上下文的逻辑,确认是否具备所需的条件来执行该操作。
设备上下文(DC)和共享上下文是否可用
我们检查了一下当前环境,发现:
- 当前仍然持有主线程创建时所用的设备上下文(DC),说明还可以使用它来创建共享上下文。
- 同时,我们也保留了主线程创建的 OpenGL 渲染上下文(RC),即主上下文。
这两个条件都具备,意味着我们可以继续尝试在这个工作线程中创建一个 共享的 OpenGL 上下文。
创建共享 OpenGL 上下文的步骤
-
在当前线程(工作线程)中:
- 使用
wglCreateContextAttribsARB
创建一个新的 OpenGL 渲染上下文。 - 并通过参数将主线程创建的 RC 作为共享上下文传入,以实现资源共享。
- 使用
-
所使用的设备上下文(DC)仍是主窗口的那个,但我们并不在这里渲染,只是借助它完成上下文的初始化。
多线程调试的一些问题说明
因为现在进入了工作线程的上下文,所以接下来需要注意:
- 多线程中对 OpenGL 的使用要小心,一般来说一个 OpenGL 上下文不能同时在多个线程中被激活(make current)。
- 我们在这里并不是让多个线程共用一个上下文,而是让每个线程有自己的上下文,并共享资源(textures、buffers 等)。
- 所以,在这个工作线程中创建的是新的上下文,但与主上下文共享资源。
接下来的操作
我们只是尝试创建这个共享的上下文,传入之前记录的:
- 设备上下文(DC)
- 主线程的 RC(共享来源)
- 创建时的一些属性配置(如 OpenGL 版本、上下文标志等)
接着我们就要测试是否能够成功创建该上下文并激活它(wglMakeCurrent
)。
小结
- 当前我们在为
needs_opengl
的工作线程创建 OpenGL 渲染上下文。 - 保留主 DC 和 RC 是为了可以创建带资源共享的上下文。
- 多线程中,每个线程有自己的 RC,但可以共享主线程的资源。
- 这是 OpenGL 多线程资源共享的常见做法。
- 需要确保主线程不提前释放 DC 或 RC,否则工作线程初始化会失败。
- 多线程下调试困难,需要特别小心 race condition 和上下文激活顺序问题。
解释调试器中发生的事情,说明两个线程正在初始化OpenGL
我们总共创建了八个线程,其中两个是用于低优先级工作的线程。它们的职责主要是执行一些后台任务,比如从磁盘加载文件和向 OpenGL 提交纹理数据。由于这些线程需要与 OpenGL 进行交互,因此必须拥有各自独立的 OpenGL 上下文。我们现在正处于为这些线程创建 OpenGL 上下文的过程中。
多线程调试与执行流程
这两个低优先级线程会分别进入一个初始化函数,在其中调用 Win32CreateOpenGLContextForWorkerThread
来创建各自的 OpenGL 上下文。我们希望可以清晰地观察其中一个线程的执行过程。
由于使用的是多线程调试器,在单步调试时会显示多个线程的执行状态,因此我们需要控制只让一个线程继续运行:
- 找到正在执行的两个线程(即正在初始化 OpenGL 的两个后台线程);
- 将其中一个线程“冻结”住;
- 同时也“冻结”主线程;
- 这样我们就可以只关注当前一个工作线程的执行。
实际调试行为
我们尝试在调试器中执行这些操作,希望让调试视图聚焦于当前工作线程的逻辑。
- 冻结另一个工作线程,避免它干扰当前观察;
- 冻结主线程,确保不会执行额外逻辑;
- 然后单步进入当前线程中的创建上下文逻辑;
- 想验证是否成功获取到了一个有效的渲染上下文(RC);
- 如果获取成功,再调用
wglMakeCurrent
将这个上下文绑定到当前线程与窗口设备上下文(DC)上; - 观察这两个操作是否都能成功完成。
遇到的问题
在尝试调试过程中发现一些异常行为:
- 虽然尝试冻结其他线程,但调试器表现得不稳定;
- 当前线程的控制权看起来没有正确交给我们;
- 甚至有时候无法成功单步执行;
- 一些线程在 CRT(运行库)中的状态不明确,可能与线程初始化机制有关。
这些问题导致我们在实际单步观察上下文创建和激活流程时有困难。
后续调试优化思路
为了解决无法观察到关键点的问题,提出一种优化方法:
- 在成功或失败后,加入一些明确的状态标记或断点行;
- 这样在调试时就能轻松判断是否执行成功;
- 例如在创建完 RC 后,打印一条日志或者设置一个状态变量;
- 这样在两个线程中执行相同流程时也能清晰地区分与追踪。
总结
- 创建了两个需要 OpenGL 的低优先级线程,需为它们单独创建上下文;
- 每个线程共享主上下文资源但拥有独立 RC;
- 使用调试器时需冻结其他线程,只观察一个线程执行;
- 线程初始化中可能进入 CRT 内部,干扰调试视图;
- 当前流程尝试验证是否成功创建并激活上下文;
- 为提升调试效率,需要加入更多可视化检查点或断点。
这一过程展示了在多线程环境下处理 OpenGL 上下文管理和调试的复杂性及注意事项。
win32_game.cpp:如果wglMakeCurrent失败,则进行断言
可以在执行 wglMakeCurrent
的地方加入一个明确的检查逻辑,如果绑定失败,就立刻进行报错处理,以便于快速识别 OpenGL 上下文是否成功激活。
背景逻辑
当前线程在尝试为其创建的 OpenGL 渲染上下文(RC)执行 wglMakeCurrent
,该函数的作用是将设备上下文(DC)与渲染上下文(RC)绑定到当前线程。
- 如果这个绑定失败,那么说明当前线程无法使用 OpenGL 进行绘制操作;
- 这是一个严重问题,需要尽早发现;
- 所以考虑加入错误检测,并在失败时立即触发断点或日志输出。
实施步骤
可以在执行 wglMakeCurrent(dc, rc)
后添加如下逻辑:
if (!wglMakeCurrent(dc, rc)) {// 打印错误或中断调试DebugBreak(); // 强制中断执行,便于调试器捕获fprintf(stderr, "wglMakeCurrent failed!\n");
}
或者:
assert(wglMakeCurrent(dc, rc) && "Failed to make OpenGL context current");
设置断点
接下来,可以在这个失败处理分支设置断点:
- 如果调试器命中这个断点,说明上下文绑定失败了;
- 这有助于更快地定位问题,比如 DC 已释放、RC 无效或线程状态异常;
- 如果从未命中这个断点,说明绑定成功,可以排除这类问题。
目的和好处
- 明确检查
wglMakeCurrent
是否成功; - 在错误发生时立刻被察觉,不会因为线程异步或日志遗漏而忽略问题;
- 帮助定位上下文绑定失败的原因,是在多线程 OpenGL 初始化中非常关键的一步;
- 加强系统的健壮性和可调试性。
总结
通过在 wglMakeCurrent
之后加入断言或断点检查机制,可以确保在 OpenGL 上下文绑定失败时第一时间捕捉到问题。这种方式是调试多线程图形程序中非常有效的手段,尤其是在上下文共享、资源竞争等问题复杂的场景中。
运行游戏并注意到我们从未触发该断言
目前的调试结果显示,在为多个线程创建 OpenGL 上下文并调用 wglMakeCurrent
的过程中,并没有命中先前设置的错误断点,这说明:
当前情况确认
- 所有需要使用 OpenGL 的工作线程,都已经成功创建了自己的 OpenGL 上下文;
- 这些上下文也都成功绑定到了各自的线程中;
- 也就是说,从系统调用的角度看,没有显性的错误或失败;
- 初始化过程是顺利的,至少在表面看来一切正常;
潜在隐患
- 成功绑定上下文 并不代表 这些上下文已经正确设置或可正常使用;
- 某些问题可能并不会在
wglCreateContext
或wglMakeCurrent
阶段暴露出来,比如:- 上下文之间的共享资源(如纹理、VBO)未配置正确;
- 某些驱动对多线程 OpenGL 上下文有隐藏限制;
- 上下文版本或配置参数不一致,导致某些高级特性无法使用;
- 这些问题可能会在运行中才逐渐显现,比如出现纹理下载失败、渲染结果异常等情况;
当前调试的结论
- 初始化过程中的“明显失败”被排除;
- 可以认为 OpenGL 多线程上下文的基础搭建是成功的;
- 当前系统处于“初步可运行”状态;
后续建议
为了进一步确保系统健壮性和正确性,建议执行以下动作:
- 验证资源共享情况:检查上下文创建时是否正确设置了共享上下文,必要时手动验证纹理或其他资源在多个上下文中是否可访问;
- 主动检测 OpenGL 错误:在关键调用之后加入
glGetError
检查,及时捕捉潜在错误; - 纹理下载路径排查:如果出现纹理加载失败,需进一步追踪加载路径、上下文状态、线程调度情况;
- 渲染一致性验证:在主线程与工作线程中渲染相同内容进行比对,验证共享资源的同步状态;
- 日志记录上下文切换信息:例如每次调用
wglMakeCurrent
时记录日志,辅助分析问题线程;
总结
当前我们已经成功为多个线程创建并绑定了 OpenGL 上下文,且未发生明显失败。虽然这不能完全保证一切无误,但它是多线程 OpenGL 系统搭建的关键第一步。接下来需要继续验证上下文之间的共享机制以及资源访问是否真正有效,以避免在后续使用中出现难以排查的图形渲染问题。
虽然那个断言没触发发现另外一个问题
调试器:步进通过Win32AllocateTexture
目前我们回到 OpenGL 相关部分,开始调试纹理下载的流程,发现了一些关键的问题和现象:
调试目标
- 关注的是纹理下载的关键函数:
Win32_AllocateTexture
Win32_DeaAllocateTexture
- 设置了断点,目的是确保纹理下载流程是否真正被触发执行。
调试过程中观察到的行为
-
断点未命中:
- 即便程序运行到一定阶段,也没有进入纹理分配和上传逻辑;
- 这说明纹理下载请求根本没有触发。
-
初步判断是逻辑问题而非 OpenGL 或多线程问题:
- 如果纹理相关路径根本没走到,说明并不是上下文创建、线程切换等底层机制出了问题;
- 问题更可能在于我们没有正确设置触发纹理加载的时机。
-
代码审查中发现问题:
- 发现一个关键字段
finalize_operation
没有被正确设置; - 导致纹理系统没有被通知需要执行加载或上传操作;
- 所以对应的代码路径自然也就从未被调用。
- 发现一个关键字段
具体问题总结
- 纹理系统的某个资源未正确标记为“需要完成”,即未设置
finalize_operation
; - 所以即使资源存在,也不会进入实际的分配和上传逻辑;
- 导致断点永远无法命中;
- 本质上是一个逻辑漏设问题,而不是线程或 API 层级的问题。
优势与影响
- 这是一个非常容易修复的 Bug;
- 避免了陷入“黑盒式调试”——那种底层线程交互难以分析的问题;
- 调试效率提升:问题直接暴露在逻辑层,避免大量系统调用追踪。
后续行动建议
-
确保 finalize_operation 被正确设置:
- 在资源状态变化时(如准备上传),务必设置该标志位;
- 可以加入断言或日志,确认哪些资源未设置该标志。
-
增强调试反馈机制:
- 在未设置 finalize_operation 而尝试操作资源时给予警告或记录日志;
- 避免这类问题悄悄发生,难以定位。
-
验证路径是否完整执行:
- 设置更多关键点断点,如资源分配初始化逻辑,验证资源是否按预期推进到下载步骤。
总结
目前纹理无法下载的根本原因,并非 OpenGL 线程上下文或驱动问题,而是逻辑层遗漏设置了关键标志位 finalize_operation
。因此整个资源上传流程从未被激活。这个问题简单直接,修复成本低,避免了更复杂的底层调试路径,是一次高效而积极的定位过程。
game_asset.cpp:将FinalizeOperation设置为FinalizeAsset_Bitmap
目前进入了一个关键的验证阶段,尝试修复之前由于未设置资源状态(finalize_operation
)导致纹理未能加载的问题,并希望这一改动就能解决全部问题。下面是详细过程与观察汇总:
当前操作目标
- 尝试设置
finalize_operation
字段,将其标记为BITMAP
类型; - 期望这样一来,资源系统就会触发对应的纹理下载逻辑;
- 希望这是导致纹理无法显示的唯一 bug。
调试与运行流程
-
设置 finalize 操作:
- 成功将某资源的
finalize_operation
字段从NONE
设置为了BITMAP
; - 表示该资源是一个 bitmap,系统应该进入 bitmap 的加载路径。
- 成功将某资源的
-
程序运行验证:
- 重新运行程序;
- 成功进入了
FinalizeAsset
的执行路径; - 这意味着资源系统识别到了需要进行最终处理的资产类型,并开始处理流程。
-
进入纹理加载流程:
- 系统在 finalize 的过程中,调用了纹理加载函数
LoadTexture
; - 这正是预期中应当发生的事情,说明流程被正确触发了。
- 系统在 finalize 的过程中,调用了纹理加载函数
发现的新问题
- 当调用
LoadTexture
时,出现了另一个问题; - 观察到可能未正确加载对应的指针或回调函数;
- 表示实际的加载函数或其地址未正确初始化或赋值;
- 有可能是某个函数指针为空,导致纹理加载过程中出现异常。
总结阶段性结论
-
之前的 bug 已修复:
finalize_operation
的未设置问题成功修复;- 程序现在能够识别资源需要被加载,并执行加载流程。
-
新问题浮出水面:
- 当前函数指针或回调未正确设置;
- 可能是
LoadTexture
函数或相关逻辑未正确初始化; - 导致加载过程没能完成,后续仍需排查。
-
调试环境表现正常:
- 能正常设置断点和进入关键函数;
- 多线程、OpenGL 上下文、任务调度等问题暂未复现;
- 问题目前集中在资源系统逻辑的完善上。
后续建议
- 检查资源系统中用于加载纹理的函数指针或委托是否为空;
- 若存在初始化函数或回调绑定,确认其在程序启动阶段已被正确执行;
- 若使用插件式或模块化加载机制,确保目标模块被正确注册;
- 增加防御性代码或日志提示,例如在加载函数调用前验证指针是否有效。
总结
目前通过设置 finalize_operation = BITMAP
成功修复了导致纹理未加载的核心问题,程序已成功进入纹理加载流程。然而在加载执行中,又发现潜在指针未初始化的问题,这可能是后续加载失败的根源。整体来看,系统逻辑已打通一大步,下一步重点转向初始化机制和函数调用绑定的排查。整体趋势乐观,问题正在逐步收敛。
这个之前做过
调试器:步进通过Win32AllocateTexture并看到游戏正确地在硬件上渲染
当前阶段主要完成了纹理在后台线程中的下载与绑定流程,并验证了整个图形资源加载路径的正确性。以下是详细中文总结:
后台纹理加载流程验证完成
- 在调用纹理分配相关逻辑时,已经能够正确调用到实际的回调函数;
- 成功获取了一个纹理句柄(如返回了句柄1,合理为首次分配);
- 对该纹理进行了绑定,并设置了相关参数;
- 成功将像素数据传输到纹理中;
- 在传输完成后,解绑了纹理,退出该调用;
- 整个过程完成了在后台线程中的纹理下载操作,且运行正常、无错误抛出。
系统表现和实际效果
- 此纹理数据的传输与OpenGL上下文处理均在低优先级线程中完成;
- 纹理的加载已经被完整地接入队列系统,与主流程进行合理的重叠与并行执行;
- 运行效果“相当顺滑”,即在当前硬件上帧率流畅,说明性能表现良好;
- 避免了主线程卡顿,资源加载对游戏运行帧率的影响已被最大限度地减少。
系统目标达成情况
- 图形资产已能在后台线程中完整加载;
- 所有精灵都通过 OpenGL 路径进行渲染;
- 排序逻辑已经到位;
- 渲染顺序正确,整体视觉输出稳定;
- 整个图形资源处理架构从加载、传输到使用,均已打通。
未完成项及未来可能补充
- 当前尚未启用或验证 sRGB 模式的帧缓冲(Framebuffer)输出;
- 理论上这应在颜色校正或 gamma 相关逻辑中加入;
- 可以通过设置 OpenGL 的
GL_FRAMEBUFFER_SRGB
等参数来实现;
- 没有进一步分析 GPU 上的行为,例如:
- 下载过程是否真正与渲染并行;
- GPU 队列是否出现阻塞;
- 缓存是否合理;
- 缺乏 GPU 可视化工具辅助分析(如 RenderDoc、NVIDIA Nsight 等)。
阶段小结
- 问题比预期简单,仅为回调函数未绑定;
- 调试纹理加载的预期花费时间大大缩短;
- 当前系统在功能和结构上已基本完成;
- 性能表现良好,已满足主要目标。
收尾
- 当前已经进入系统稳定阶段;
- 可能不再需要继续排查纹理加载部分;
- 后续可根据开发时间和需求,考虑是否优化 framebuffer、色彩空间、GPU 数据分析等更高级部分。
总结语
我们已成功实现并验证了后台线程纹理加载流程,整个图形资源加载与使用路径完全打通,系统运行顺畅,渲染逻辑严谨,资源管理健全,初步目标达成。接下来若无新需求,可考虑进入图形优化或功能扩展阶段。
两个线程进来allocate
奇怪
主函数可以
不能在子线程中wglCreateContextAttribsARB
打算在主线程中wglCreateContextAttribsARB 让后把OpenGLRC 拷贝到子线程中去 然后wglMakeCurrent(Thread->OpenGLDC, Thread->OpenGLRC)
bind纹理也没问题
GLuint Handle = 0; 方便看有么有成功
为什么还是没有显示
要刷新一下
退出有问题
提到纹理下载和渲染之间没有同步,并描述我们关于失败情况的目标
我们在实现纹理下载的时候,是通过一个独立的线程来完成的。具体来说,纹理数据的加载发生在线程函数中,而这个线程是低优先级的 worker 线程之一,它负责执行实际的下载操作。问题出现在这个设计背后有一个非常关键的点,就是下载线程和渲染线程之间没有进行同步。
当我们在后台线程中下载纹理,同时在主渲染线程中使用 OpenGL 进行绘制时,OpenGL 默认不会帮我们进行操作序列化。也就是说,它不会自动等待纹理加载完成才去使用这个纹理进行绘制操作。如果我们两个线程分别在不同的 OpenGL 上下文中进行操作(比如一个上传纹理,一个绘制),那么 OpenGL 并不会自动帮我们等待上传完成。这种行为虽然可能在某些实现下被“自动序列化”了,但从规范角度讲,这是不被强制要求的。
结果就是,如果下载的纹理还没有完成传输,我们的渲染命令却已经使用了这个纹理,就会在屏幕上绘制出错误的图像,比如乱码、未初始化的数据、旧的图像等。这就是我们常说的“绘制垃圾”现象。
我们不打算通过牺牲帧率来避免这种情况。换句话说,我们更倾向于保留一点点图像上的错误,也不愿意让游戏出现卡顿或掉帧。因为对我们来说,持续保持 60 帧是最重要的目标,卡顿或延迟比画面出现一帧的异常纹理更容易破坏游戏体验,尤其是在动作类游戏中。
如果我们尝试解决这个问题,理论上可以设计一个系统,当发现纹理未准备好时,就先用一个临时的、低分辨率的纹理替代。这种机制其实在一些游戏引擎中被尝试过,比如《Rage》这种使用流式纹理的游戏,但用户仍然反馈这种方式看起来很“卡”,说明这种补救措施并不能完全解决问题。
因此我们目前的策略是尽可能地在预测和数据预取上下功夫,让纹理尽量提前准备好,减少未加载完成时被使用的概率。目标是做到始终准时加载,确保纹理在被渲染之前就已准备就绪,从而避免绘制错误,同时保持流畅的游戏体验。
此外,我们也会考虑硬件方面的差异,比如机械硬盘可能会因为读取速度慢而更容易出现问题,因此我们可能也会在发布时明确告知,想获得良好体验需要更现代的硬件支持,比如固态硬盘和较强的 GPU。
最后还有一个未完成的点是我们目前还没有开启帧缓冲的 sRGB 写入支持,虽然代码中是有相关设置的,但具体启用的位置不太确定,后续可能会回头处理这一部分。整体上,这就是我们关于纹理流式加载处理的完整思路与策略。
game_opengl.cpp:解释伽玛校正的重要性
我们目前讨论的是关于 OpenGL 中帧缓冲和 sRGB 相关的处理,重点在于如何启用正确的颜色空间转换,确保最终图像的色彩表现准确。
我们已经在某处启用了 GL_FRAMEBUFFER_SRGB
,这个设置的作用是告诉 OpenGL:当我们将像素颜色输出到帧缓冲时,需要自动将线性空间的颜色转换为 sRGB 空间的颜色。这个转换就类似于我们以前在软件光栅器中手动做的 Gamma 校正。
我们当时编写软件光栅器的目的,是为了理解图形渲染中低层次的细节,比如颜色空间转换,Gamma 校正等等。当时我们做了两类 Gamma 相关的处理:
-
纹理加载阶段:我们在加载纹理时,会把它从 sRGB 空间转换成线性空间,也就是在上传纹理到显卡之前做转换。这样我们在使用纹理采样时,数据就是线性的,可以直接用于光照计算。
-
帧缓冲读取阶段:当我们从帧缓冲中读取颜色值,比如为了混合半透明像素时,我们也需要将帧缓冲中的颜色值从 sRGB 空间转换成线性空间,然后再进行颜色混合运算。
虽然这种转换对最终图像的影响通常很小,特别是当画面中没有半透明效果或复杂叠加时,它基本上是感知不到的。但在一些细节部分,比如透明图层边缘、抗锯齿区域、或是多个半透明图层混合时,这种差异就会变得可见。如果不做正确的颜色空间处理,颜色混合会不准确,导致视觉上的瑕疵。
更重要的是,理解这种处理对于未来更复杂的渲染系统是非常重要的。比如在构建支持多光源的延迟渲染系统、多重光照通道组合、全局光照等高级渲染功能时,Gamma 处理和颜色空间的转换将直接影响图像质量。
目前我们虽然启用了 GL_FRAMEBUFFER_SRGB
,但我们还没有告诉 OpenGL:窗口帧缓冲本身就是一个 sRGB 帧缓冲。也就是说,虽然我们启用了 sRGB 写入功能,但默认情况下,如果窗口的默认帧缓冲不是以 sRGB 格式创建的,那么这个功能是不会生效的。我们还需要进一步设置或确认帧缓冲格式,以确保输出图像在最终写入屏幕时进行正确的 Gamma 编码。
总结来说,目标是确保我们的渲染输出,在进入最终帧缓冲之前能够正确地从线性空间转换为 sRGB,从而在各种显示设备上都能保持一致而准确的色彩表现。同时也为未来更复杂的光照和渲染技术做好基础准备。
game_opengl.cpp:解释为什么有两个独立的sRGB启用选项
我们在使用 OpenGL 的时候,可能会疑惑为什么启用 sRGB 渲染需要两个不同的步骤,其实这是 OpenGL 的一个设计特点——它同时在“全局状态”和“对象状态”上都存储设置。
这意味着,某些行为不是只设置一次就能生效,而是必须在多个层级分别启用。具体到 sRGB 渲染,OpenGL 要求我们既要在 OpenGL 的全局状态中启用 sRGB 写入功能(GL_FRAMEBUFFER_SRGB
),又要确保当前绑定的帧缓冲本身是以 sRGB 格式创建的。
这可能听起来有点混乱,但背后的原因是 OpenGL 支持同时存在多个帧缓冲(Framebuffers),而且它希望这些帧缓冲可以根据用途灵活配置,比如一些可以启用 sRGB 渲染,另一些保持线性空间。
比如我们可能会有一系列中间的渲染目标(例如后处理的临时纹理),以及一个最终的帧缓冲用于输出到屏幕。在这种复杂的渲染流程中,我们可能希望部分缓冲区启用 sRGB,而其他保持线性空间。OpenGL 的设计就是为了支持这种混合使用。
所以它的行为如下:
- 我们通过
glEnable(GL_FRAMEBUFFER_SRGB)
启用 sRGB 渲染功能,表示当当前帧缓冲是 sRGB 格式时,OpenGL 会自动将线性颜色转换成 sRGB 颜色再写入。 - 然而,这个功能只有在绑定的帧缓冲本身就是 sRGB 格式的情况下才会生效。也就是说,帧缓冲本身也要标记为“这是一个 sRGB 缓冲区”。
这两个条件缺一不可。如果只启用了 GL_FRAMEBUFFER_SRGB
,但帧缓冲不是 sRGB 格式,那么不会有任何 sRGB 写入发生;同理,如果帧缓冲是 sRGB 格式,但没有开启 GL_FRAMEBUFFER_SRGB
,OpenGL 也不会进行颜色空间转换。
从设计角度来看,这样做的目的就是为了给开发者更细粒度的控制权,使得可以灵活切换不同帧缓冲的颜色空间需求。但这种设计也带来了额外的复杂性,因为我们必须手动保证这两个部分都设置正确,否则就会出现颜色不准确的问题。
虽然我们认为这样的设计并不理想,甚至有些不直观,但它背后还是有一定逻辑的,尤其是在处理复杂多通道渲染流程时,确实提供了一定的灵活性。
总结:
- 必须在 OpenGL 的状态中启用
GL_FRAMEBUFFER_SRGB
。 - 同时,绑定的帧缓冲也必须是以 sRGB 格式创建的。
- 两者同时满足时,OpenGL 才会进行线性到 sRGB 的颜色转换写入。
- 这个机制允许我们在多个渲染通道之间混合使用 sRGB 和线性缓冲,虽然不够友好但功能完整。
互联网:查阅OpenGL文档
我们现在的目标是让 OpenGL 支持在窗口帧缓冲中启用 sRGB 渲染。在理想情况下,只需要在设置像素格式时简单地指定 sRGB 格式,比如在 PIXELFORMATDESCRIPTOR
中加个 sRGB
标志就可以完成,但现实并非如此。
Windows 上的 WGL(Windows OpenGL)接口对现代帧缓冲特性的支持是通过一套扩展机制实现的。这意味着想启用 sRGB 等新特性,必须使用扩展版的像素格式选择函数,而不能使用传统的 ChoosePixelFormat
和 DescribePixelFormat
。
我们目前使用的像素格式选择流程主要依赖老旧的 API,比如:
ChoosePixelFormat
DescribePixelFormat
SetPixelFormat
其中,前两个负责查询和描述系统支持的像素格式,第三个设置实际使用的像素格式。
为了使用支持 sRGB 的帧缓冲,我们需要用 WGL 的扩展版本来替代这两个函数。具体步骤如下:
1. 使用 WGL 扩展函数
我们要切换到使用以下扩展函数:
wglChoosePixelFormatARB
wglGetPixelFormatAttribivARB
wglGetPixelFormatAttribfvARB
这些函数支持更多现代像素格式的选项,包括 sRGB 支持。
2. 定义需要的像素格式属性
我们需要传入这些函数一个属性列表,告诉它我们需要什么,比如:
- RGBA 格式
- 支持 OpenGL
- 支持双缓冲
- 支持窗口绘制
- 支持 sRGB 帧缓冲(通过
WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB
)
也就是说,WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB
是我们要关注的核心标志,它告诉系统我们希望所选择的像素格式支持 sRGB。
3. 替代过程中的函数调用
我们将:
- 用
wglChoosePixelFormatARB
替代ChoosePixelFormat
- 用
wglGetPixelFormatAttribivARB
替代DescribePixelFormat
- 保持
SetPixelFormat
不变(它仍然只需要一个整数 index)
也就是说,SetPixelFormat
这一环节仍然能继续使用,因为它本质上只需要像素格式的索引值,而这个索引值是我们用扩展函数选出来的。
4. 问题点和不确定性
当前还存在一些不确定的细节:
- 不完全确定哪些常量用于描述 sRGB 支持(可能需要从其它扩展定义中获取常量值)
- 也许需要查询驱动是否支持
WGL_ARB_framebuffer_sRGB
扩展 wglChoosePixelFormatARB
使用时需要一个当前有效的 OpenGL 上下文才能调用,因此初始化阶段需要先创建一个临时上下文用于加载扩展函数。
小结:
要在 Windows 上正确启用 OpenGL 的 sRGB 帧缓冲输出,我们必须:
- 使用 WGL 的扩展函数
wglChoosePixelFormatARB
和wglGetPixelFormatAttribivARB
- 指定像素格式属性
WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB
为 TRUE - 最终使用
SetPixelFormat
设定所选格式 - 确保驱动和系统支持这些扩展功能
虽然过程较为繁琐,但这是实现现代 OpenGL 渲染管线中颜色空间控制(例如 Gamma 校正)的关键一步。之后我们就可以利用 OpenGL 自动将线性空间颜色正确地转换为 sRGB 写入帧缓冲,从而得到更准确的颜色输出。
win32_game.cpp:为这些WINAPI wgl_*函数定义typedef并在Win32InitOpenGL中启用扩展
在本段讨论中,首先提到了对OpenGL上下文的创建和使用过程。关键内容包括通过WGL(Windows OpenGL扩展)API调用来设置像素格式和查询扩展功能,进而处理图形渲染任务。具体涉及如何获取OpenGL扩展函数,并用这些扩展函数来设置不同的像素格式,以便在不同的硬件或环境下更高效地进行渲染。
为了正确获取像素格式,需要执行一系列的步骤。首先,需要通过wglGetProcAddress
获取所需的扩展函数。如果扩展函数无法获取,则退回到传统的像素格式设置方式。并且,涉及的函数调用包括了查询扩展和设置像素格式的函数,如wglChoosePixelFormatARB
等。在设置像素格式时,可能需要根据硬件支持的不同属性来选择适合的格式,比如双缓冲、RGB颜色、透明度、交换缓冲等。
接下来,讨论了如何通过wglGetProcAddress
来获取扩展函数,这个过程可能会受到上下文创建的影响。为了确保能够正确执行,需要先创建一个OpenGL上下文,并且在这个上下文中才能正确获取和使用扩展函数。此外,还需要确保创建的上下文与所选择的像素格式匹配,并且某些OpenGL调用可能依赖于特定的上下文状态。
在实际操作中,有时需要在OpenGL上下文设置后,动态地调整像素格式或其他属性。此时,可以通过调用一些特定的API函数来查询当前状态,并根据实际需求修改。例如,通过wglMakeCurrent
来设定当前上下文和设备上下文的关联,从而在渲染时能够使用正确的像素格式。
此外,还提到了如何通过查询扩展功能来调整渲染设置,具体涉及到像素格式的详细属性,例如颜色位数、支持的缓冲区类型、是否支持双缓冲等。这些属性的选择会直接影响到渲染性能和效果,因此在选择合适的像素格式时,需要综合考虑多种因素。
在调试和测试过程中,发现需要确保OpenGL上下文已经被正确初始化才能执行查询操作。如果在初始化过程中没有正确设置上下文,某些操作可能会失败。因此,正确的上下文管理和初始化流程是确保程序稳定运行的关键。
总体来说,本段内容主要讲述了如何在Windows平台上使用WGL接口来管理OpenGL上下文,获取和使用扩展功能,并选择适合的像素格式进行渲染。处理这些任务时需要关注设备上下文的管理、扩展函数的加载和错误处理等多个方面。
https://registry.khronos.org/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt
OpenGL的像素格式选择相关,具体来说,它们是WGL(Windows OpenGL)扩展的部分,用于查询和设置像素格式时的属性。以下是每个宏定义的详细说明:
-
WGL_DRAW_TO_WINDOW_ARB 0x2001
- 作用:此属性用于指定该像素格式是否支持渲染到窗口。它是一个布尔值,值为
TRUE
表示可以渲染到窗口,FALSE
表示不支持渲染到窗口。
- 作用:此属性用于指定该像素格式是否支持渲染到窗口。它是一个布尔值,值为
-
WGL_ACCELERATION_ARB 0x2003
- 作用:此属性指定OpenGL渲染的加速方式。其值可以是:
WGL_NO_ACCELERATION_ARB
:没有加速。WGL_GENERIC_ACCELERATION_ARB
:使用软件加速(即通过CPU进行渲染)。WGL_FULL_ACCELERATION_ARB
:使用硬件加速(即通过GPU进行渲染)。
- 作用:此属性指定OpenGL渲染的加速方式。其值可以是:
-
WGL_SUPPORT_OPENGL_ARB 0x2010
- 作用:此属性指定该像素格式是否支持OpenGL。如果该值为
TRUE
,表示该像素格式可以被OpenGL使用。如果是FALSE
,则该像素格式不支持OpenGL。
- 作用:此属性指定该像素格式是否支持OpenGL。如果该值为
-
WGL_FULL_ACCELERATION_ARB 0x2027
- 作用:此属性用于描述像素格式是否支持完全硬件加速,通常与
WGL_ACCELERATION_ARB
一起使用。如果像素格式支持完全硬件加速,则返回TRUE
。
- 作用:此属性用于描述像素格式是否支持完全硬件加速,通常与
-
WGL_DOUBLE_BUFFER_ARB 0x2011
- 作用:此属性指定像素格式是否支持双缓冲。双缓冲技术是图形渲染中常用的技术,使用两个缓冲区(前缓冲和后缓冲)来避免屏幕撕裂和闪烁。在双缓冲模式下,渲染会先在后台缓冲区完成,完成后再交换前后缓冲区的内容。
-
WGL_TYPE_RGBA_ARB 0x202B
- 作用:此属性指定像素格式的颜色类型。
WGL_TYPE_RGBA_ARB
表示该像素格式支持RGBA(红、绿、蓝、透明度)颜色模型。RGBA是最常见的颜色模型,适用于大多数图形渲染任务。
- 作用:此属性指定像素格式的颜色类型。
-
WGL_PIXEL_TYPE_ARB 0x2013
- 作用:此属性指定像素格式的类型。值可以为:
WGL_TYPE_COLORINDEX_ARB
:颜色索引类型,用于使用颜色索引的像素格式(较旧的图形系统)。WGL_TYPE_RGBA_ARB
:RGBA类型,用于现代图形渲染系统。
- 作用:此属性指定像素格式的类型。值可以为:
这些宏定义都是与WGL扩展相关的,用于查询和设置窗口中的像素格式属性,进而为OpenGL创建适当的渲染环境。它们提供了关于是否支持硬件加速、是否支持双缓冲、是否支持OpenGL等的信息,开发者通过这些设置可以确保OpenGL渲染在合适的环境中进行。
wglChoosePixelFormatARB
是一个扩展函数,用于选择适合当前窗口的像素格式。它是 WGL(Windows OpenGL)扩展的一部分,允许开发者根据具体的属性要求(如颜色深度、双缓冲、支持的OpenGL版本等)来选择合适的像素格式。
函数原型
BOOL wglChoosePixelFormatARB(HDC hdc, // 设备上下文句柄const int *piAttribIList, // 属性整数数组const float *pfAttribFList, // 属性浮动数组UINT nMaxFormats, // 可返回的最大像素格式数量int *piFormats, // 用于存储符合条件的像素格式的数组UINT *nNumFormats // 实际符合条件的像素格式数量
);
参数解析
-
hdc
(HDC
):
设备上下文句柄,表示窗口的绘图环境。通过该句柄,WGL可以知道要为哪个窗口选择像素格式。 -
piAttribIList
(const int *
):
这是一个整数数组,用来指定像素格式选择的属性。通常包含一些常见的属性,例如:WGL_DRAW_TO_WINDOW_ARB
: 是否支持渲染到窗口WGL_SUPPORT_OPENGL_ARB
: 是否支持OpenGLWGL_ACCELERATION_ARB
: 渲染加速方式(如无加速、软件加速或硬件加速)WGL_DOUBLE_BUFFER_ARB
: 是否支持双缓冲- 等等。
-
pfAttribFList
(const float *
):
这是一个浮动数组,包含某些像素格式的属性要求(通常是浮点值)。例如:WGL_PIXEL_FORMAT_ARB
: 表示期望的像素格式。
-
nMaxFormats
(UINT
):
这是返回的最大像素格式数,指定了能够容纳多少个符合要求的像素格式。如果只关心一个格式,可以将其设置为 1。 -
piFormats
(int *
):
这是一个整数数组,用来存储符合条件的像素格式的索引。函数会根据给定的属性要求返回满足条件的像素格式的索引。 -
nNumFormats
(UINT *
):
这是一个输出参数,用来返回实际选择的像素格式的数量。它表示返回的符合条件的像素格式的数量。
函数的作用
wglChoosePixelFormatARB
的作用是根据指定的属性要求,选择并返回一个或多个符合条件的像素格式。通常在创建OpenGL上下文时,开发者需要选择一个合适的像素格式(如颜色深度、缓冲区类型、加速方式等),而这个函数就是用于根据这些需求选择最适合的格式。
返回值
TRUE
: 成功,函数找到一个或多个符合条件的像素格式,并将其索引存储在piFormats
数组中。FALSE
: 失败,没有找到符合要求的像素格式。
示例使用
int iPixelFormat;
UINT numFormats;
int attribs[] = {WGL_DRAW_TO_WINDOW_ARB, TRUE,WGL_SUPPORT_OPENGL_ARB, TRUE,WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,WGL_DOUBLE_BUFFER_ARB, TRUE,WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,0
};if (wglChoosePixelFormatARB(hdc, attribs, NULL, 1, &iPixelFormat, &numFormats)) {if (numFormats > 0) {// 使用找到的像素格式}
}
在这个例子中,wglChoosePixelFormatARB
被调用以选择支持OpenGL、硬件加速、双缓冲并且是RGBA类型的像素格式。如果找到了符合条件的格式,它将返回第一个格式的索引 iPixelFormat
。
总结
wglChoosePixelFormatARB
允许开发者根据具体的属性要求(如硬件加速、双缓冲等)选择合适的像素格式。这对于在Windows平台上创建高效的OpenGL上下文非常重要。
根据C11和C++11内存模型,当你在工作线程中设置纹理句柄并在渲染线程中读取它而没有任何同步时,难道不会有竞争条件吗?我认为你需要使用原子操作,否则编译器可能会搞乱生成的代码
在讨论多线程和内存模型时,特别是关于如何在工作线程中设置纹理句柄并在渲染线程中读取它而不进行同步时,确实有一些潜在的问题。这个情境可能会引发竞态条件,特别是在涉及现代内存模型时,比如C11和C++11的内存模型。简而言之,竞态条件可能导致数据竞争,使得纹理句柄在渲染线程读取时变得不可预测或者错误。
根据C11和C++11的内存模型,它们规定了多线程并发访问共享数据时可能会发生的行为。更具体地说,C11和C++11的内存模型在多线程访问共享资源时具有一定的规定,然而也存在一些复杂的未定义行为。这就意味着,如果没有适当的同步机制(如原子操作或者锁),内存中的数据可能处于不一致的状态。例如,工作线程可能在渲染线程读取纹理句柄时,已经更改了该数据,导致渲染线程使用了一个无效或未更新的纹理句柄。
在这种情况下,编译器的优化可能会导致代码行为不符合预期。由于编译器通常会优化掉一些不必要的内存读取或写入操作,如果没有显式的同步机制来保证内存操作的顺序,编译器可能会将数据读取或写入重新排序,从而导致不可预期的行为。换句话说,编译器可能会因为没有明确的同步指令而“误解”开发者的意图,生成错误的代码。
为了解决这个问题,通常需要使用一些同步机制,如原子操作或锁。这些机制可以确保内存访问的顺序性,防止竞态条件的发生,确保一个线程对数据的修改能够及时且正确地反映在其他线程中,避免渲染线程在读取纹理句柄时获取到无效数据。原子操作可以帮助确保对共享数据的修改是“不可分割”的,从而避免竞态条件和由此带来的潜在错误。
关于C11和C++11内存模型的复杂性,它们确实在某些情况下会引发未定义的行为,这使得它们在并发编程中变得相当复杂和难以掌控。对于一些不符合标准的行为,可能没有严格的编译器检查或限制,因此开发者在进行低级内存操作时需要格外小心,确保所有多线程数据访问都经过适当的同步。
总的来说,如果在没有适当同步的情况下从不同线程访问共享资源,确实可能会导致问题。原子操作可以解决大多数问题,而编译器的优化不会主动干预同步机制。
为什么不直接在Molly中渲染到后台缓冲区?
在讨论渲染时,我们可能面临需要渲染到一个较低分辨率的情况,特别是在一些计算能力较低的设备上,比如当用户的显示器分辨率很高(如4K),但显卡的性能不足以支撑该分辨率时。在这种情况下,通常的做法是将渲染目标设置为一个较低的分辨率,比如1080p,而不是直接渲染到显示器的原始分辨率。
这种方法的好处在于,显卡能够以较低的分辨率进行渲染,减少性能压力。然后,渲染完成后,会使用一个最终的处理步骤,将该低分辨率的图像放大到实际显示器的分辨率。这种方式避免了直接更改显示器的分辨率,从而避免了可能引发的各种问题,比如桌面图标的显示异常等问题,特别是Windows在调整分辨率时常会出现的一些不便。
这时,渲染并不是直接到显示器的后缓冲区,而是渲染到一个中间的缓冲区,之后通过一个上采样过程将其放大到显示器的实际分辨率。通常,这种方式不需要直接从后缓冲区读取数据,问题的关键在于是否会读取后缓冲区的内容。
特别需要注意的是,只有在从后缓冲区读取数据时,sRGB标志才会发挥作用。如果不涉及读取操作,通常不需要设置sRGB模式。具体来说,如果只是将一个缓冲区的内容复制到另一个缓冲区,而不是直接进行渲染,通常不会有问题,因为没有涉及色彩空间的转换。
但是,如果使用的是线性颜色空间的着色器进行写入操作,或者需要对缓冲区做色彩空间的转换,那么sRGB标志是必须的。举例来说,如果我们将sRGB图像直接复制到一个已经在sRGB颜色空间中的后缓冲区,那么就不需要进行任何色彩空间转换,因此不需要特别标记该缓冲区为sRGB。
在实际操作中,可能还会遇到某些情况,需要确保图像的上采样在更精确的颜色空间下进行。比如,如果我们使用sRGB模式进行图像拉伸,理论上可以获得更准确的插值效果,使得图像的放大更平滑。这是因为,使用sRGB进行处理时,颜色空间的变换会更加准确,尤其是在执行双线性插值时。
总的来说,在处理低分辨率渲染和图像放大时,选择合适的缓冲区和颜色空间设置非常重要。通过使用适当的渲染技术和色彩空间管理,能够确保即便在性能有限的情况下,也能获得较为精细的渲染效果。
仅供参考,不,设置像素格式后是不能更改的。需要创建一个临时窗口和上下文,获取wgl*arb函数指针,然后销毁上下文和窗口,重新创建新窗口和上下文,并使用wgl*arb。这很麻烦,但在Windows上就是这样工作的,我知道的
在Windows系统中,像OpenGL这样的图形API通常会遇到一些限制,特别是在涉及到像素格式(pixel format)设置时。一旦像素格式被设置好,就无法直接修改。如果需要改变像素格式,通常的做法是创建一个临时窗口和OpenGL上下文,然后通过获取WGL(Windows OpenGL)函数指针来进行相关操作。这种方法可能看起来有些繁琐,尤其是在Windows环境下,因为微软更倾向于推广DirectX,而非OpenGL。
实际上,微软本身并不直接支持OpenGL,它的图形API通常是以DirectX为主,而OpenGL只是通过第三方驱动和开发者的努力得以运行。因此,在Windows上,OpenGL的驱动和相关功能并不像DirectX那样原生支持,往往需要通过一些间接方式来实现。
通常情况下,OpenGL的驱动不会在没有创建OpenGL上下文的情况下加载。这就意味着,必须先创建一个上下文,才能进行OpenGL相关的操作。如果想要改变像素格式,或者在不创建窗口的情况下进行其他操作,就需要利用临时窗口来创建OpenGL上下文,这样驱动才会被加载,并能够执行相关操作。虽然这种方式看起来不太直观,甚至可能显得有些麻烦,但这是在Windows平台上绕过一些限制的可行方法。
这种方法需要开发者创建一个空窗口(dummy window),然后利用该窗口创建OpenGL上下文,进行必要的设置和操作。虽然这可能需要一些额外的工作,但实际上并不是特别困难,通常情况下可以通过一些简单的代码实现。在实际开发中,遇到这类问题时,可以先采用这种临时窗口的方法,然后进一步进行调试和优化。
我们会在之后回到调试代码并完成它吗?还是我们现在已经完成了?
在完成当前的调试工作后,可能会继续进行其他任务,但从目前情况来看,调试工作还没有完全结束。完成调试后,可能会回到最初的工作进度,继续处理条形码的相关部分。
一旦调试任务完成,如果没有其他重要的任务需要处理,工作进度应该会恢复到原来的方向。不过目前来看,并没有特别紧急的事项需要处理,调试完成后就可以继续着手于其他任务,尤其是条形码的处理,作为接下来的主要工作重点。
感谢详细的解释。你会在稍后讲解现代GL吗?
目前的计划是,可能会涉及到一些现代化的技术,尤其是关于光照的部分,可能会需要用到一些射线追踪(shooters)。不过,关于顶点缓冲区的内容,似乎没有必要进一步探讨,因为这个部分不需要在当前的工作中使用,因此不需要做过多的深入研究。
总体而言,接下来的工作可能会专注于一些实际需要的技术,比如光照方面的处理,而顶点缓冲区等内容则不会成为工作中的重点,因此不会进一步深入。
显然在DirectX 12中,你可以为不同的GPU选择不同的上下文
在 DirectX 12 中,可能存在选择不同上下文(contexts)来处理不同任务的功能,但具体能做到什么程度还不清楚。由于没有直接使用 DirectX 的经验,所以无法确认这些功能是否与 OpenGL 类似。OpenGL 中过去是不能直接在批量处理时使用不同的上下文,至少在之前是如此。
至于 DirectX 12,假设它与 DirectX 11 不差异太大,理论上可能也没有这个功能,虽然可能有人认为 DirectX 11 会支持这一功能,但实际上似乎并不具备。
是的,D3D11也是如此
考虑到微软的策略,许多人认为 DirectX 可能早就支持选择不同的上下文来处理不同的任务,因为微软鼓励开发者使用 DirectX,并且在平台上有控制权。因此,很多人猜测 DirectX 很早就可能加入了这一功能。然而,具体是哪一版 DirectX 添加了这个功能,目前还不清楚。
你难道不能使用PBO来流式传输纹理吗?
关于使用像素缓冲对象(PBO)进行纹理流的方式,首先需要明确的是,这种方法是否能带来显著的好处。通常情况下,如果目标是减少纹理的碎片化问题,可以考虑使用几个大纹理,并将多个小纹理打包到这些大纹理中。然后,使用 glTexSubImage
方法从中提取出特定的区域,而不是频繁调用 glTexImage
来替换整个纹理。
但在大多数情况下,除非游戏在某种方式上遇到了性能瓶颈或者特定的问题,否则并没有明显的理由一定要选择某一种方法。对于是否使用像素缓冲对象(PBO),如果没有强烈的需求或经验,可能没有必要特别去选择它,因为目前并没有明显的优势,除非能具体解决某些性能或资源管理的问题。
相关文章:
游戏引擎学习第244天: 完成异步纹理下载
启动并运行游戏,注意到我们的纹理没有被下载 我们将继续完成游戏的开发。昨天,我们已经实现了多线程的纹理下载,但并没有时间调试它,因此纹理下载功能目前并没有正常工作。我们只是写了相关的代码,但由于大部分时间都…...
【安全扫描器原理】TCP/IP协议编程
【安全扫描器原理】TCP/IP协议编程 1.概述2.Windows Socket结构3.Windows socket转换类函数4.Windows Socket通信类函数 1.概述 TCP/IP协议是目前网络中使用最广泛的协议,Socket称为“套接口”,最早出现在Berkeley Unix中,最初只支持TCP/I…...
Cuda-GDB Frame Unwind 管理(未完.)
在计算机编程中,Frame Unwind(栈展开) 是指函数调用栈的逆向操作,即在函数返回或异常发生时,系统逐层释放栈帧(Stack Frame)、恢复调用上下文的过程。以下是详细解释及其在 GPU编程(…...
如何在IDEA中高效使用Test注解进行单元测试?
在软件开发过程中,单元测试是保证代码质量的重要手段之一。而IntelliJ IDEA作为一款强大的Java开发工具,提供了丰富的功能来支持JUnit测试,尤其是通过Test注解可以快速编写和运行单元测试。那么,如何在IDEA中高效使用Test注解进行…...
什么是访客鉴权?全面解析核心原理与CC防护应用实践
一、访客鉴权是什么? 访客鉴权(Visitor Authentication and Authorization)是系统对访问者进行身份验证和权限控制的过程,确保只有合法用户能够访问特定资源或执行特定操作。其核心目标是确认身份、控制权限、保障数据安全&#…...
DeepSeek大模型应用学习通知
随着人工智能在各领域深度融合发展,DeepSeek大模型迅速火爆全网,清华大学以最快的速度发布了DeepSeek从入门到精通使用技巧,能够更好的助力于企业和个人参与到AI研究和应用中,对于AI行业创新有重要意义,被誉为国运级的…...
时间序列预测模型比较分析:SARIMAX、RNN、LSTM、Prophet 及 Transformer
时间序列预测根据过去的模式预测未来事件。我们的目标是找出最佳预测方法,因为不同的技术在特定条件下表现出色。本文章将探讨各种方法在不同数据集上的表现,为你在任何情况下选择和微调正确的预测方法提供真知灼见。 我们将探讨五种主要方法࿱…...
快速了解redis,个人笔记
更多个人笔记:(仅供参考,非盈利) gitee: https://gitee.com/harryhack/it_note github: https://github.com/ZHLOVEYY/IT_note (基于mac展示,别的可以参考)接下来将直接…...
Dify依赖管理poetry切换为uv
Dify升级 1.3.0 后api的依赖管理从poetry切换为了 uv管理,但是官网暂时还没有更新。 升级 tag:Dify 1.3.0版本 在此记录一下 uv 依赖管理操作 使用方法 [重要事项] 在 v1.3.0 版本中,poetry 已被[ uv ](https://docs.astral.sh/uv/) 替代…...
VGA 接口静电防护方案
VGA(Video Graphics Array)即视频图形阵列,具有分辨率高、显示速 率快、颜色丰富等优点,亦称为 D-Sub 接口,在彩色显示器领域得到了广 泛的应用, 如笔记本、投影仪、LCD 液晶显示屏 等。VGA 接口主要用于连接 计算机与显示设备。当…...
MySQL 详解之用户、权限与审计:保障数据安全的基石
在数据库系统中,数据是核心资产,对其的访问必须受到严格控制。谁能连接到数据库?他们能看到哪些数据?能执行哪些操作(读、写、修改结构)?系统中的所有操作是否被记录以便追溯?这正是用户管理、权限系统和审计机制需要解决的问题。 在 MySQL 中: 用户 (Users): 负责认…...
力扣面试150题--环形链表和两数相加
Day 32 题目描述 思路 采取快慢指针 /*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {public boolean hasCycle(ListNod…...
HMI与组态,自动化的“灵珠”和“魔丸”
在现代工业自动化领域,组态(Configuration)和人机界面(HMI,Human-Machine Interface)是两个核心概念,它们在智能化控制系统中发挥着至关重要的作用。尽管这两者看似简单,但它们的功能…...
AbMole| CU-CPT-8m(CAS号125079-83-6;目录号M9746)
CU-CPT-8m是一种特异性的TLR8(toll-like receptor 8)拮抗剂,其IC50值为67 nM,Kd值为220 nM。 生物活性 CU-CPT-8m是一种特异性的TLR8(toll-like receptor 8)拮抗剂,其IC50值为67 nM,…...
【网络入侵检测】基于源码分析Suricata的PCAP模式
【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 本文聚焦于 Suricata 7.0.10 版本源码,深入剖析其 PCAP 模式的实现原理。通过系统性拆解初始化阶段的配置流程、PCAP 数据包接收线程的创建与运行机制,以及数据…...
【滑动窗口+哈希表/数组记录】Leetcode 438. 找到字符串中所有字母异位词
题目要求 给定两个字符串 s 和 p,找到 s 中所有 p 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。 示例 1 输入:s…...
uniapp自定义封装tabbar
uniapp自定义封装tabbar 开发原因: 有很多时候 小程序并没有其类目 需要通过配置发布审核, ps:需要去掉项目pages.json tabbar配置,不然重进会显示默认,跳转页面不能uni.switchTab。 组件tabbar <template><viewclass&…...
uni-app云开发总结
uni-app云开发总结 云开发无非就三个概念:云数据库、云函数、云存储 uni-app中新增了一个概念叫做云对象,它其实就是云函数的加强版,它是导出的一个对象,对象中可以包含多个操作数据库的函数,接下来咱们就详细对uni-…...
uniapp-商城-37-shop 购物车 选好了 进行订单确认3 支付栏
支付栏 就是前面用的 car-Layout 在shop也用来这个组件 只是在那里用来的是购物车。 1、 样式 我们开始进入这个页面是点击的shop的购物篮 到这里就变成了支付栏 其实他们是同一个组件 只是做了样式区分 2、具体看看样式和代码 2.1 消失了购物车和改变了按钮名字 如何…...
搜索二叉树-key的搜索模型
二叉搜索树(Binary Search Tree, BST)是一种重要的数据结构,它有两种基本模型:Key模型和Key/Value模型。 一、Key模型 1.基本概念 Key模型是二叉搜索树中最简单的形式,每个节点只存储一个键值(key),没有额外的数据值(value)。这…...
Qt ModbusSlave多线程实践总结
最近项目中用到了ModbusSlave,也就是Modbus从设备的功能,之前用的基本都是master设备,所以读取数据啥的用单线程就行了,用 void WaitHelper::WaitImplByEventloop(int msec) {QEventLoop loop;QTimer::singleShot(msec, &loop…...
Leetcode刷题记录18——接雨水
题源:https://leetcode.cn/problems/trapping-rain-water/description/?envTypestudy-plan-v2&envIdtop-100-liked 题目描述: 思路一: 🌟 本题核心思想:木桶效应 每个位置的“桶”:假设每个柱子的位…...
IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤
以下是在 IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤: 步骤 1:创建 Maven Web 项目 新建项目 File -> New -> Project → 选择 Maven → 勾选 Create from archetype → 选择 maven-archetype-webapp。输入 GroupId(如 com.examp…...
全球玻璃纸市场深度洞察:环保浪潮下的材料革命与产业重构(2025-2031)
一、行业全景:从传统包装到绿色经济的战略支点 玻璃纸(Cellulose Film),即再生纤维素薄膜,以木浆、棉浆等天然纤维素为原料,通过碱化、黄化、成型等工艺制成,兼具透明性、柔韧性及100%生物降解性…...
提示js方法未定义,但是确实<textarea>标签未闭合。
1、问题现象。 Uncaught ReferenceError: showOtherDismantleFn is not defined 但是这个方法,在代码中明明存在。 #if($!{isNewEnergy})#if($!{batteryName} 宁德时代)<button class"btn btn-info btn-xs" onclick"showNingDismantleFn()&quo…...
spring中的@bean注解详解
在Spring框架中,Bean注解是用于显式声明一个Bean的核心方式之一,尤其在基于Java的配置中。Spring框架中的Bean注解实现原理涉及多个核心机制,包括配置类解析、Bean定义注册、动态代理及依赖注入等 一、Bean注解的作用 Bean用于标注在方法上&…...
计算机网络中的DHCP是什么呀? 详情解答
目录 DHCP 是什么? DHCP 的工作原理 主要功能 DHCP 与网络安全的关系 1. 正面作用 2. 潜在安全风险 DHCP 的已知漏洞 1. 协议设计缺陷 2. 软件实现漏洞 3. 配置错误导致的漏洞 4. 已知漏洞总结 举例说明 DHCP 与网络安全 如何提升 DHCP 安全性 总结 D…...
uniapp-商城-38-shop 购物车 选好了 进行订单确认4 配送方式1
配送方式在订单确认页面最上方,可以进行选中配送还是自提,这里先看看配送。 代码样式: 可以看出来是通过组件来实现的。组件名字是:delivery-layout 1、建立组件文件夹和页面,delivery-layout这里就只有配送 2、具体…...
粒子群优化算法(Particle Swarm Optimization, PSO)的详细解读
最近研究基于进化算法的神经网络架构搜索,仔细阅读了TEVC2023年发表的一篇NAS搜索的文章,觉得收益颇多,对比NSGA-2,这里给出PSO的详细解释。【本人目前研究的是多目标进化算法,欢迎交流、留言】 文章题目是࿱…...
大模型在直肠癌预测及治疗方案制定中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 研究方法与创新点 二、大模型技术概述 2.1 大模型的基本原理 2.2 常见大模型类型及特点 2.3 在医疗领域的应用进展 三、直肠癌预测相关数据收集与处理 3.1 数据来源 3.2 数据清洗与预处理 3.3 特征工程 四、大…...
【C++】继承----下篇
文章目录 前言一、实现一个不能继承的类二、友元与继承三、继承与静态成员四、多继承以及菱形继承问题1.继承模型:2.菱形继承的问题3.虚拟继承解决数据冗余和二义性的原理4.虚拟继承的原理 五、继承的总结和反思1.继承和组合 总结 前言 各位好呀!今天呢我们接着讲继…...
windows安装jax和jaxlib的教程(cuda)成功安装
本文你将解决3个问题:1、jaxlib没有安装的问题;2、python3.9以上(不可忽略)、cuda12.1(可忽略)以上配置要求不满足的问题;3、numpy版本太高的问题。 1、问题描述 当你直接pip install jax或者c…...
软考【网络工程师】2023年5月上午题答案解析
1、固态硬盘的存储介质是()。 A 光盘 B 闪存 C 软盘 D 磁盘 答案是 B。 固态硬盘(Solid State Drive),简称 SSD,是用固态电子存储芯片阵列制成的硬盘,其存储介质是闪存(Flash Memory)。闪存具有非易失性,即在断电后仍能保留存储的数据,且读写速度快、抗震性强、能…...
支付场景下,乐观锁的实现(简洁版)
1、问题描述 看到一个同事建的数据库表,好奇打开看看。 create table db_paycenter.t_pay_order_divide (id bigint auto_increment comment 主键id|20250402|XXXprimary key,user_id bigint not null comment user…...
AI视频技术赋能幼儿园安全——教师离岗报警系统的智慧守护
教师离岗报警系统如一位无形的守护者,实时监测教室动态,一旦发现教师离岗超30秒,立即通知园方,确保幼儿不被忽视。这套开源系统以高效检测和即时报警为核心,助力园所优化管理,增强家长信心,开启…...
SCI论文结构笔记
摘要五要素(Abstract): 背景和研究问题研究目的研究方法研究结果结论和意义 引言(Introduction): 研究背景研究问题研究现状现有的研究的问题与不足本研究的研究目标文章结构 研究综述(Literature review): 选题的理由现存文献中可借鉴的…...
《修仙家族模拟器2》:游戏背景故事介绍!
《修仙家族模拟器2》构建了一个以修仙文明为根基的宗族传承世界,玩家将扮演家族初代掌舵者,在动态演变的修仙江湖中完成从凡俗世家到仙道巨擘的蜕变。以下为具体背景设定解析: 一、世界观架构:仙凡交织的修真宇宙 空间维度 游戏…...
Linux部署ragflow,从安装docker开始~
安装docker https://download.docker.com/linux/static/stable/x86_64/docker-28.0.1.tgz #首先创建一个文件夹,存放我们需要的各类文件,并切换到该目录 mkdir /project && cd /project #此时我们的工作目录已经切换到刚刚创建的文件夹下了,接…...
苹果iosApp提交审核常见问题--内购订阅篇
常见问题1- 准则2.1.1 Guideline 2.1 - Information Needed The app binary includes the PassKit framework for implementing Apple Pay, but we were unable to verify any integration of Apple Pay within the app. Next Steps If the app integrates the functionali…...
从代码学习深度学习 - 微调 PyTorch 版
文章目录 前言一、迁移学习与微调概念二、微调步骤解析三、实战案例:热狗识别3.1 数据集准备3.2 图像增强处理3.3 加载预训练模型3.4 模型重构3.5 差异化学习率训练3.6 对比实验分析总结前言 深度学习模型训练通常需要大量数据,但在实际应用中,我们往往难以获得足够的标记数…...
Registry镜像仓库的安装与使用
任务目标 (1)了解目前主流的镜像仓库 (2)掌握registry私有镜像仓库的部署与使用 任务实施 基础信息 Docker私有仓库个宿主机配置信息 主机名 IP地址 节点角色 registry 192.168.110.80 私有仓库 node1 192.168.110.9…...
java多线程(6.0)
目录 编辑 阻塞队列 阻塞队列概念 生产者消费者模型 阻塞队列的作用 阻塞队列的使用 阻塞队列的实现 阻塞队列 阻塞队列概念 阻塞队列是一种特殊的队列,同样遵循“先进先出”的原则,支持入队操作和出队操作和一些基础方法。在此基础上&#…...
tkinter的文件对话框:filedialog
诸神缄默不语-个人技术博文与视频目录 文章目录 一、前言二、tkinter.filedialog模块详解2.1 模块导入方式2.2 通用参数说明 三、五大核心函数实战3.1 选择单个文件 - askopenfilename()3.2 多文件选择 - askopenfilenames()3.3 保存文件对话框 - asksaveasfilename()3.4 选择目…...
HOW - 如何模拟实现 gpt 展示答案的交互效果
文章目录 产品设计维度核心目标实现方式主要靠一些技巧1. 用 emoji 做语义锚点2. 每个段落只传达一件事3. 有节奏地对话式切换4. 使用 Markdown 风格来排版5. 用“你”而不是“用户”说话 如果想实现类似体验(比如写文档、教程、产品介绍) 前端开发维度想…...
达梦数据库压力测试报错超出全局hash join空间,适当增加HJ_BUF_GLOBAL_SIZE解决
1.名词解释:达梦数据库中的HJ_BUF_GLOBAL_SIZE是所有哈希连接操作可用的最大哈希缓冲区大小,单位为兆字节(MB) 2.达梦压测报错: 3.找到达梦数据库安装文件 4.压力测试脚本 import http.client import multiprocessi…...
第11章 面向分类任务的表示模型微调
第1章 对大型语言模型的介绍第2章 分词和嵌入第3章 解析大型语言模型的内部机制第4章 文本分类第5章 文本聚类与主题建模第6章 提示工程第7章 高级文本生成技术与工具第8章 语义搜索与检索增强生成第9章 多模态大语言模型第10章 构建文本嵌入模型第12章 微调生成模…...
c#加密证件号的中间部分,改为*号
前言 使用场景:在我项目中,我需要给前端提供接口,所以我要吧证件号进行加密。例如:411421199510225612,这是一个身份证号,18为的,那么我加密完成之后就会是 411421********5612,类似…...
qt中写一个简易的计算器
以下是添加了详细代码注释的版本: cpp #include <iostream>using namespace std;定义加法函数(已注释掉) //int add(int a, int b) { // return a b; //}定义减法函数(已注释掉) //int min(int a, int b) {…...
[特殊字符] Docker 从入门到实战:全流程教程 + 项目部署指南(含镜像加速)
Docker 是现代 DevOps 的基石,应用广泛于微服务、CI/CD、K8s、云原生等场景。本文将从 0 到 1 手把手带你掌握 Docker 的核心知识点,并完成 Java Nginx 项目部署,适合新手与进阶开发者阅读与实战。 📚 目录 Docker 快速入门 入门…...
《R语言SCI期刊论文绘图专题计划》大纲
今天开始,我将和大家分享系统且详细的《R语言SCI期刊绘图专题教程》,内容会从基础到高阶应用,从配色美学到顶刊风格复现,确保大家可以学到高质量内容!下面是大纲。 📚《R语言SCI期刊论文绘图专题计划》 第…...