4. WebGPU 存储缓冲区 (WebGPU Storage Buffers)
这篇文章是关于存储缓冲区的,我们从上一篇文章暂停的地方继续。
存储缓冲区在许多方面类似于统一缓冲区。如果我们所做的只是将 JavaScript 中的 UNIFORM 更改为 STORAGE 并将 WGSL 中的 var 更改为 var<storage, read> ,那么上一页中的示例就可以正常工作。
其实区别就在这里,不用重命名变量就可以有更合适的名字。
const staticUniformBuffer = device.createBuffer({label: `static uniforms for obj: ${i}`,size: staticUniformBufferSize,// usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,});...const uniformBuffer = device.createBuffer({label: `changing uniforms for obj: ${i}`,size: uniformBufferSize,// usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,});
在我们的 WSGL 中
@group(0) @binding(0) var<storage, read> ourStruct: OurStruct;@group(0) @binding(1) var<storage, read> otherStruct: OtherStruct;
无需其他更改即可正常工作,就像以前一样
统一缓冲区和存储缓冲区之间的差异 Differences between uniform buffers and storage buffers
统一缓冲区和存储缓冲区之间的主要区别是:
-
对于特定的用例,统一缓冲区可以更快
这真的取决于用例。一个典型的应用程序需要绘制很多不同的东西。对于3D游戏,该应用程序可能会绘制汽车、建筑物、岩石、灌木丛、人等……每一个都需要传递与我们上面的示例类似的方向和材料属性。在这种情况下,使用统一缓冲区是推荐的解决方案。 -
存储缓冲区可以比统一缓冲区大得多。
The minimum maximum size of a uniform buffer is 64k
统一缓冲区的最小最大大小为64k
The minimum maximum size of a storage buffer is 128meg
存储缓冲区的最小最大大小为 128megBy minimum maximum, there is a maximum size a
buffer of certain type can be. For uniform buffers that maximum size
is at least 64k. For storage buffers it’s at least 128meg. We’ll
cover limits in another article.
通过最小最大值,某种类型的缓冲区可以达到最大大小。对于最大大小至少为
64k 的统一缓冲区。对于存储缓冲区,它至少为 128 兆。我们将在另一篇文章中介绍限制。
3.存储缓冲区可以读/写,统一缓冲区是只读的
我们在第一篇文章的计算着色器示例中看到了写入存储缓冲区的示例。
鉴于上面的前两点,让我们以最后一个示例为例,将其更改为在一次绘制调用中绘制所有 100 个三角形。这是一个可能适合存储缓冲区的用例。我说可能是因为,WebGPU 与其他编程语言相似。有很多方法可以实现同一件事,比如 array.forEach 对比 for (const elem of array) 对比 for (let i = 0; i < array.length; ++i) 。每个都有它的用途。 WebGPU 也是如此。我们尝试做的每一件事都有多种实现方式。当谈到绘制三角形时**,WebGPU 关心的只是我们从顶点着色器返回 builtin(position) 的值**,并从片段着色器返回 location(0) 的颜色/值。 见【注释1】
我们要做的第一件事是将存储声明更改为运行时大小的数组。
// @group(0) @binding(0) var<storage, read> ourStruct: OurStruct;
// @group(0) @binding(1) var<storage, read> otherStruct: OtherStruct;
@group(0) @binding(0) var<storage, read> ourStructs: array<OurStruct>;
@group(0) @binding(1) var<storage, read> otherStructs: array<OtherStruct>;
然后我们将更改着色器以使用这些值
@vertex fn vs(@builtin(vertex_index) vertexIndex : u32,@builtin(instance_index) instanceIndex: u32
) -> @builtin(position) {var pos = array<vec2f, 3>(vec2f( 0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);let otherStruct = otherStructs[instanceIndex];let ourStruct = ourStructs[instanceIndex];return vec4f(pos[vertexIndex] * otherStruct.scale + ourStruct.offset, 0.0, 1.0);
}
我们向顶点着色器添加了一个名为 instanceIndex 的新参数,并赋予它 @builtin(instance_index) 属性,这意味着它从 WebGPU 为绘制的每个“实例”获取其值。当我们调用 draw 时,我们可以传递实例数的第二个参数,对于绘制的每个实例,正在处理的实例数将传递给我们的函数。
使用 instanceIndex 可以从结构数组中获取指定的结构元素。
We also need to some get the color from the correct array element and use it in our fragment shader. The fragment shader doesn’t have access to @builtin(instance_index) because that would make no sense. We could pass it as an inter-stage variable but it would be more common to look up the color in the vertex shader and just pass the color.
我们还需要从正确的数组元素中获取颜色,并在我们的片段着色器中使用它。片段着色器无法访问 @builtin(instance_index) ,因为那没有任何意义。我们可以将它作为阶段间变量传递,但更常见的做法是在顶点着色器中查找颜色并传递颜色。
为此,我们将使用另一个结构,就像我们在关于阶段间变量的文章中所做的那样
struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
}@vertex fn vs(@builtin(vertex_index) vertexIndex : u32,@builtin(instance_index) instanceIndex: u32
// ) -> @builtin(position) vec4f {
) -> VSOutput {var pos = array<vec2f, 3>(vec2f( 0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);let otherStruct = otherStructs[instanceIndex];let ourStruct = ourStructs[instanceIndex];// return vec4f(// pos[vertexIndex] * otherStruct.scale + ourStruct.offset, 0.0, 1.0);var vsOut: VSOutput;vsOut.position = vec4f(pos[vertexIndex] * otherStruct.scale + ourStruct.offset, 0.0, 1.0);vsOut.color = ourStruct.color;return vsOut;
}// @fragment fn fs() -> @location(0) vec4f {
// return ourStruct.color;
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}
现在我们已经修改了 WGSL 着色器,让我们更新 JavaScript。
代码如下:
const kNumObjects = 100;const objectInfos = [];// create 2 storage buffersconst staticUnitSize =4 * 4 + // color is 4 32bit floats (4bytes each)2 * 4 + // offset is 2 32bit floats (4bytes each)2 * 4; // paddingconst changingUnitSize =2 * 4; // scale is 2 32bit floats (4bytes each)const staticStorageBufferSize = staticUnitSize * kNumObjects;const changingStorageBufferSize = changingUnitSize * kNumObjects;const staticStorageBuffer = device.createBuffer({label: 'static storage for objects',size: staticStorageBufferSize,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,});const changingStorageBuffer = device.createBuffer({label: 'changing storage for objects',size: changingStorageBufferSize,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,});// offsets to the various uniform values in float32 indicesconst kColorOffset = 0;const kOffsetOffset = 4;const kScaleOffset = 0;{const staticStorageValues = new Float32Array(staticStorageBufferSize / 4);for (let i = 0; i < kNumObjects; ++i) {const staticOffset = i * (staticUnitSize / 4);// These are only set once so set them nowstaticStorageValues.set([rand(), rand(), rand(), 1], staticOffset + kColorOffset); // set the colorstaticStorageValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], staticOffset + kOffsetOffset); // set the offsetobjectInfos.push({scale: rand(0.2, 0.5),});}device.queue.writeBuffer(staticStorageBuffer, 0, staticStorageValues);}// a typed array we can use to update the changingStorageBufferconst storageValues = new Float32Array(changingStorageBufferSize / 4);const bindGroup = device.createBindGroup({label: 'bind group for objects',layout: pipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: { buffer: staticStorageBuffer }},{ binding: 1, resource: { buffer: changingStorageBuffer }},],});
上面我们创建了 2 个存储缓冲区。一个用于 OurStruct 数组,另一个用于 OtherStruct 数组。
然后我们用偏移量和颜色填充 OurStruct 数组的值,然后将该数据上传到 staticStorageBuffer 。
我们只创建一个引用两个缓冲区的绑定组。
新的渲染代码是
function render() {// Get the current texture from the canvas context and// set it as the texture to render to.renderPassDescriptor.colorAttachments[0].view =context.getCurrentTexture().createView();const encoder = device.createCommandEncoder();const pass = encoder.beginRenderPass(renderPassDescriptor);pass.setPipeline(pipeline);// Set the uniform values in our JavaScript side Float32Arrayconst aspect = canvas.width / canvas.height;//for (const {scale, bindGroup, uniformBuffer, uniformValues} of objectInfos) {// uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale// device.queue.writeBuffer(uniformBuffer, 0, uniformValues);// pass.setBindGroup(0, bindGroup);// pass.draw(3); // call our vertex shader 3 times// }// set the scales for each objectobjectInfos.forEach(({scale}, ndx) => {const offset = ndx * (changingUnitSize / 4);storageValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});// upload all scales at oncedevice.queue.writeBuffer(changingStorageBuffer, 0, storageValues);pass.setBindGroup(0, bindGroup);pass.draw(3, kNumObjects); // call our vertex shader 3 times for each instancepass.end();const commandBuffer = encoder.finish();device.queue.submit([commandBuffer]);}
上面的代码将绘制 kNumObjects 实例。对于每个实例,WebGPU 将调用顶点着色器 3 次, vertex_index 设置为 0、1、2, instance_index 设置为 0 到 kNumObjects - 1
我们绘制了 100 个三角形,每个三角形具有不同的比例、颜色和偏移量。对于您想要绘制同一对象的大量实例的情况,这是一种实现方法。
顶点数据使用存储缓冲区
到目前为止,我们一直在着色器中直接硬编码三角形。存储缓冲区的一个用例是存储顶点数据。就像我们在上面的示例中通过 instance_index 索引当前存储缓冲区一样,我们可以使用 vertex_index 索引另一个存储缓冲区来获取顶点数据。
我们开始吧!
struct OurStruct {color: vec4f,offset: vec2f,
};struct OtherStruct {scale: vec2f,
};struct Vertex {position: vec2f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@group(0) @binding(0) var<storage, read> ourStructs: array<OurStruct>;
@group(0) @binding(1) var<storage, read> otherStructs: array<OtherStruct>;
@group(0) @binding(2) var<storage, read> pos: array<Vertex>;@vertex fn vs(@builtin(vertex_index) vertexIndex : u32,@builtin(instance_index) instanceIndex: u32
) -> VSOutput {//var pos = array<vec2f, 3>(// vec2f( 0.0, 0.5), // top center// vec2f(-0.5, -0.5), // bottom left// vec2f( 0.5, -0.5) // bottom right//);let otherStruct = otherStructs[instanceIndex];let ourStruct = ourStructs[instanceIndex];var vsOut: VSOutput;vsOut.position = vec4f(pos[vertexIndex].position * otherStruct.scale + ourStruct.offset, 0.0, 1.0);vsOut.color = ourStruct.color;return vsOut;
}@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}
现在我们需要为一些顶点数据再设置一个存储缓冲区。首先让我们创建一个函数来生成一些顶点数据。大概是一个圆。
function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2,
} = {}) {// 2 triangles per subdivision, 3 verts per tri, 2 values (xy) each.const numVertices = numSubdivisions * 3 * 2;const vertexData = new Float32Array(numSubdivisions * 2 * 3 * 2);let offset = 0;const addVertex = (x, y) => {vertexData[offset++] = x;vertexData[offset++] = y;};// 2 vertices per subdivision//// 0--1 4// | / /|// |/ / |// 2 3--5for (let i = 0; i < numSubdivisions; ++i) {const angle1 = startAngle + (i + 0) * (endAngle - startAngle) / numSubdivisions;const angle2 = startAngle + (i + 1) * (endAngle - startAngle) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first triangleaddVertex(c1 * radius, s1 * radius);addVertex(c2 * radius, s2 * radius);addVertex(c1 * innerRadius, s1 * innerRadius);// second triangleaddVertex(c1 * innerRadius, s1 * innerRadius);addVertex(c2 * radius, s2 * radius);addVertex(c2 * innerRadius, s2 * innerRadius);}return {vertexData,numVertices,};
}
上面的代码用这样的三角形制作了一个圆
所以我们可以用它来用圆的顶点填充存储缓冲区
// setup a storage buffer with vertex dataconst { vertexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25,});const vertexStorageBuffer = device.createBuffer({label: 'storage buffer vertices',size: vertexData.byteLength,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer(vertexStorageBuffer, 0, vertexData);
And then we need to add it to our bind group.
然后我们需要将它添加到我们的绑定组中。const bindGroup = device.createBindGroup({label: 'bind group for objects',layout: pipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: { buffer: staticStorageBuffer }},{ binding: 1, resource: { buffer: changingStorageBuffer }},{ binding: 2, resource: { buffer: vertexStorageBuffer }},],});
最后在渲染时,我们需要要求渲染圆圈中的所有顶点。
pass.draw(3, kNumObjects); // call our vertex shader 3 times for several instancespass.draw(numVertices, kNumObjects);
上面我们用了
struct Vertex {pos: vec2f;
};@group(0) @binding(2) var<storage, read> pos: array<Vertex>;
我们可以不使用 struct 而直接使用 vec2f 。
@group(0) @binding(2) var<storage, read> pos: vec2f;
但是,通过使它成为一个结构,以后添加顶点数据不是更容易吗?
通过存储缓冲区传递顶点越来越受欢迎。有人告诉我,尽管一些较旧的设备比我们将在接下来的一篇关于顶点缓冲区中介绍的经典方法慢。
【注释1】We can have multiple color attachments and then we’ll need to return more colors/value for location(1), location(2), etc… ↩︎
我们可以有多个颜色附件,然后我们需要为 location(1) 、 location(2) 等返回更多颜色/值……↩︎
相关文章:

4. WebGPU 存储缓冲区 (WebGPU Storage Buffers)
这篇文章是关于存储缓冲区的,我们从上一篇文章暂停的地方继续。 存储缓冲区在许多方面类似于统一缓冲区。如果我们所做的只是将 JavaScript 中的 UNIFORM 更改为 STORAGE 并将 WGSL 中的 var 更改为 var<storage, read> ,那么上一页中的示例就可以…...
ChatGPT 插件功能深度解析:acquire、scholarai、form
引言 在我们的日常工作中,插件扮演着重要的角色,它们可以帮助我们提高工作效率,简化复杂的任务。在这篇文章中,我将详细介绍三个非常实用的插件:acquire、scholarai和form。 1、acquire 插件详解 acquire插件是一个…...
【面试集锦 - 汽车电子 - ASPICE]
ASPICE ASPICE(Automotive Software Performance Improvement and Capability dEtermination)是一种针对汽车电子行业的软件过程评估和改进模型。它是一种国际标准,旨在帮助汽车制造商和供应商评估和改进其软件开发过程的能力,以…...
深入探索Vue.js响应式原理及其实现机制
导语:Vue.js的核心特性之一是其强大的响应式系统,它使得数据和视图能够自动保持同步。在本文中,我们将深入探索Vue.js的响应式原理及其实现机制,帮助您更好地理解Vue.js的工作方式。 数据劫持:Vue.js的响应式系统通过数…...

Spark SQL概述、数据帧与数据集
文章目录 一、准备工作1、准备数据文件2、启动Spark Shell 二、加载数据为Dataset1、读文件得数据集 三、给数据集添加元数据信息1、定义学生样例类2、导入隐式转换3、将数据集转换成学生数据集4、对学生数据集进行操作(1)显示数据集内容(2&a…...
c# cad 二次开发 类库 CAD表格的操作,给CAD添加一个表格
c# cad 二次开发 类库 CAD表格的操作,给CAD添加一个表格 using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Colors; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using A…...
单点登录的两种实现方式,分别有啥优缺点?
单点登录(Single Sign-On,简称SSO)是指在多个应用系统中,用户只需要登录一次,就可以访问所有已授权的系统资源的一种身份认证技术。SSO可以提升用户体验,减少用户密码管理工作量,并加强安全管理…...

opencv_c++学习(二十七)
一、单目相机模型 上图为针孔相机成像原理,蓝色坐标中的O即为镜头光心。成像原理与小孔成像相同。 单目相机映射关系如下: 将上式进行变换,就可以从三位空间映射到2维平面的公式。 相机的畸变公式如下: 二、模型投影函数 vo…...
探查chatGPT插件:Outschool,resume,webhooks
引言 在我们的日常工作和学习中,插件扮演着重要的角色。它们可以帮助我们提高效率,简化复杂的任务。在这篇文章中,我将介绍三个非常有用的插件:Outschool,resume,和webhooks,并通过具体的例子来…...

【学习笔记】Unity基础(七)【uGUI基础、利用render Texture实现小地图功能】
目录 一 Canvas1.1 三种Render Space渲染空间 screen1.2 canvas scaler画布缩放器1.3sprite1.4 sprite packer1.5 unity目录1.6 RuleTile Tilemap1.7 sprite packer1.8 sorting layer 二 rect transform2.1 pivot 中轴 中心点2.2 anchor 锚点2.3 uGUI源代码 三 EventSystem3.1 …...

yolov5配置错误记录
这里是直接没有找到数据集,说明是路径错误。经过设置yaml后, # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path: ../autodl-tmp/datasets/neu # dataset root dir tr…...
全平台数据 (数据库) 管理工具 DataCap 1.10.0 发布
当前版本涉及几个主要更新。 DataCap 已发布 发布版本发布时间1.10.02023-05-30 General 修复服务启动默认连接 mongo修复了 sql 模板的 h2 db update_time 和 create_time改进 H2 元数据管理获取类型改进 mysql 元数据管理获取类型固定元数据管理数据页默认为 1重构数据渲染…...

使用Mybatis接口开发
文章目录 目录 前言 公司项目用到了mybatis开发接口,虽然很简单,但是mybatis不是特别熟悉,这里学习一下 一、Mybatis接口绑定的两种方式 1.接口绑定实现方式 就是在接口的方法上加上Select,updateInsertDelete等注解 select注解介绍: 简便,能快速去操作sql,它只需要在mapper…...
数据采集技术的实现原理有哪些?
数据采集技术是指通过各种手段和技术手段,从互联网、移动设备、传感器等各种数据源中获取数据,并将其存储、处理和分析,以便为业务决策和应用提供支持。本文将介绍数据采集技术的实现原理,包括数据采集的基本流程、数据采集技术的…...
2023年数学建模随机森林:基于多个决策树的集成学习方法
2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 目录 目录 1. 什么是随机森林? 2. 随机森林的优缺点 3. 随机森林的构建过程...

OpenAI发布最新研究让大模型数学推理直接达到SOTA
🦉 AI新闻 🚀 OpenAI发布最新研究:基于过程奖励的监督方法,让大模型数学推理直接达到SOTA 摘要:OpenAI最新研究基于GPT-4微调,采用过程监督和结果监督两种监督方法,奖励每个正确推理步骤的过程…...
快速检测 GlassFish 任意文件读取漏洞的 Python 脚本
部分数据来源:ChatGPT 引言 当下,互联网安全问题正愈发严重,黑客利用各种漏洞进行攻击的频率也在持续增加。在2015年10月,一位名为“路人甲”的安全研究员在乌云上公开了一个名为“应用服务器glassfish存在通用任意文件读取漏洞”的漏洞(编号:wooyun-2010-0144595),该…...

Docker镜像更新通知器DIUN
什么是 DIUN ? Docker Image Update Notifier 是一个用 Go 编写的 CLI 应用程序,可作为单个可执行文件和 Docker 映像交付,用于当 Docker 映像在 Docker registry中更新时接收通知。 和老苏之前介绍过的 watchtower 不同,DIUN 只是通知&…...

插件框架PF4J-从理论到实践
PF4J:Plugin Framework for Java 目录 是什么? 不是什么? 特点 组件 主要类 流程概述 spring-pf4j 思考 功能模块化 我对pf4j的封装和使用demo GitHub - chlInGithub/pf4jDemo: pf4j demo 是什么? 开源轻量级的插件框架。通过插件…...

怎么将pdf文件免费转为扫描件
推荐两个工具,也算是给自己记一下 1、手机:扫描全能王APP 太好使了,可以直接拍照并转换为扫描件 不开会员的话会出现水印,因为我都是自己用或者交作业就没开 支持读取相册,一次一张、多张都可以 如果不想要水印也…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...