二、WebGPU阶段间变量(inter-stage variables)
二、WebGPU阶段间变量(inter-stage variables)
在上一篇文章中,我们介绍了一些关于WebGPU的基础知识。在本文中,我们将介绍阶段变量(inter-stage variables)的基础知识。
阶段变量在顶点着色器和片段着色器之间起作用。当顶点着色器输出3个位置时,三角形将栅格化。顶点着色器可以在每个位置输出额外的值,默认情况下,这些值将在3个点之间进行插值。让我们举个小例子。我们将从上一篇文章中的三角形着色器开始。
我们要做的就是改变着色器。
const module = device.createShaderModule({label: 'our hardcoded rgb triangle shaders',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);var color = array<vec4f, 3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1), // blue);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;}@fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;}`,});
首先,我们声明一个结构体。这是一个在顶点着色器和片段着色器之间协调阶段间变量的简单方法。
struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};
然后我们声明顶点着色器来返回这种类型的结构
@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {
我们创建一个包含3种颜色的数组。
var color = array<vec4f, 3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1), // blue);
然后不是返回一个vec4f来获取位置,而是我们声明一个结构的实例,填充它,然后返回它
var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;
在片段着色器中,我们声明它将这些结构之一作为函数的参数
@fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;}
然后返回颜色。
如果我们运行它,我们会看到,每次GPU调用我们的片段着色器时,它都会传递在所有3个点之间插值的颜色。
以下为当前代码及运行结果:
HTML:
<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-17 16:33:32* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="003color-triangle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./003color-triangle.ts"></script></body></html>
TS:
/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-09-17 21:06:44* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "hardcoded rgb triangle pipeline",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs"},fragment: {module:shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"// topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});function frame() {const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.draw(3, 1, 0, 0);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });
Shaders:
shader:
struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
}@vertex
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {let pos = array<vec2f, 3>(vec2f(0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f(0.5, -0.5) // bottom right);var color = array<vec4f,3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1) // blue);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;
}@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;
}
阶段间变量最常用于跨三角形插值纹理坐标,我们将在纹理文章中介绍。另一个常见的用法是插值法线穿过三角形,这将在第一篇文章中介绍照明。
阶段变量按位置连接
重要的一点是,就像WebGPU中几乎所有的东西一样,顶点着色器和片段着色器之间的连接是通过索引的。对于阶段间变量,它们通过位置索引连接。
为了了解我的意思,让我们只更改片段着色器,在location(0)处采用vec4f参数,而不是结构体。
@fragment fn fs(@location(0) color: vec4f) -> @location(0) vec4f {return color;}
下面两个片段着色器的代码是同等效果的,依然可以渲染出渐变色的三角形。
@builtin(position)
我们的原始着色器在顶点和片段着色器中使用相同的结构,有一个名为position的字段,但它没有位置。它被声明为@builtin(position)。
struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};
该字段不是阶段间变量。相反,它是内置的。碰巧@builtin(position)在顶点着色器和片段着色器中有不同的含义。
在顶点着色器中,@builtin(position)是GPU在片段着色器中绘制三角形/线/点所需的输出。
在片段着色器中,@builtin(position)是一个输入,是片段着色器当前被要求计算颜色的像素的像素坐标。
像素坐标由像素的边缘指定。提供给片段着色器的值是像素中心的坐标。
如果我们要绘制的纹理大小为3x2像素,这些就是坐标。
我们可以改变我们的着色器来使用这个位置。例如,让我们画一个棋盘。
const module = device.createShaderModule({label: 'our hardcoded checkerboard triangle shaders',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;}@fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {let red = vec4f(1, 0, 0, 1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(fsInput.position.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);}`,});
position 被声明为@builtin(position),它会将xy坐标转换为vec2u,后者是两个无符号整数。然后将它们除以8,得到每8个像素增加一次的计数。然后,它将x和y网格坐标相加,计算模块2,并将结果与模块1进行比较。这将给我们一个布尔值,每隔一个整数就为true或false。最后,它使用WGSL函数select 给定2个值,根据布尔条件选择其中一个。在JavaScript中,select是这样写的
// If condition is false return `a`, otherwise return `b`
select = (a, b, condition) => condition ? b : a;
代码及运行结果:
HTML:
<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-17 16:33:32* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="004color-grid-triangle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./004color-grid-triangle.ts"></script></body></html>
TS:
/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-09-17 21:06:44* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "hardcoded rgb triangle pipeline",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs"},fragment: {module:shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"// topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});function frame() {const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.draw(3, 1, 0, 0);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });
Shaders:
shader:
struct OurVertexShaderOutput {@builtin(position) position: vec4f
}@vertex
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {let pos = array<vec2f, 3>(vec2f(0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f(0.5, -0.5) // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;
}@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {let red = vec4f(1,0,0,1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(fsInput.position.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);
}
即使你在片段着色器中不使用@builtin(position),它的存在也很方便,因为它意味着我们可以在顶点着色器和片段着色器中使用同一个结构体。需要注意的是,顶点着色器和片段着色器中的position结构体字段是完全不相关的。它们是完全不同的变量。
如上所述,对于阶段间变量,重要的是@location(?)。因此,为顶点着色器的输出和片段着色器的输入声明不同的结构体是很常见的。
为了让这个更清楚,在我们的例子中,顶点着色器和片段着色器在同一个字符串中只是为了方便。我们也可以将它们分成单独的模块。
const vsModule = device.createShaderModule({label: 'hardcoded triangle',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;}`,});const fsModule = device.createShaderModule({label: 'checkerboard',code: `@fragment fn fs(@builtin(position) pixelPosition: vec4f) -> @location(0) vec4f {let red = vec4f(1, 0, 0, 1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(pixelPosition.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);}`,});
我们必须更新创建的管道才能使用它们
const pipeline = device.createRenderPipeline({label: 'hardcoded checkerboard triangle pipeline',layout: 'auto',vertex: {module: vsModule,entryPoint: 'vs',},fragment: {module: fsModule,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});
这里 demo 只更改了 fragment 的代码,效果等同:
关键是,在大多数WebGPU示例中,两个着色器使用相同的字符串只是为了方便。实际上,首先WebGPU解析WGSL以确保其语法正确。然后,WebGPU查看你指定的入口点。从那里开始,它会查看入口点引用的部分,而不是该入口点的其他部分。它很有用,因为如果两个或多个着色器共享绑定、结构、常量或函数,就不需要两次输入结构、绑定和分组位置等内容。但是,从WebGPU的角度来看,就好像您为每个入口点复制了所有它们一样。
注意:使用@builtin(position)生成棋盘并不常见。棋盘或其他图案更常用纹理来实现。实际上,如果调整窗口的大小,就会出现问题。因为棋盘是基于画布的像素坐标,所以它是相对于画布的,而不是相对于三角形的。
插值设置
我们在上面看到,阶段间变量,顶点着色器的输出,在传递给片段着色器时进行插值。有两组设置可以改变插值的发生方式。将它们设置为默认值以外的任何值并不常见,但有一些用例将在其他文章中介绍。
插值类型:
- perspective:值以正确的透视方式(默认)插值。
- linear:值以线性的、非透视的正确方式插值。
- falt:值不进行插值。插值采样不用于平面插值
插值采样(Interpolation sampling):
- center:在像素的中心执行插值(默认)
- centroid:在当前基元内的碎片覆盖的所有样本内的一点执行插值。这个值对于原始类型中的所有样本都是相同的。
- sample:对每个样本进行插值。应用这个属性时,每个样本都会调用一次片段着色器。
将它们指定为属性。例如:
@location(2) @interpolate(linear, center) myVariableFoo: vec4f;@location(3) @interpolate(flat) myVariableBar: vec4f;
请注意,如果阶段间变量是整数类型,则必须将其插值设置为平坦 flat。
如果将插值类型设置为flat,则传递给片段着色器的值就是该三角形中第一个顶点的 变量的值。
在下一篇文章中,我们将介绍uniform作为传递数据到着色器的另一种方法。
相关文章:

二、WebGPU阶段间变量(inter-stage variables)
二、WebGPU阶段间变量(inter-stage variables) 在上一篇文章中,我们介绍了一些关于WebGPU的基础知识。在本文中,我们将介绍阶段变量(inter-stage variables)的基础知识。 阶段变量在顶点着色器和片段着色…...
【Linux】31个普通信号
文章目录 1.每种信号的含义2.两种不能被忽略的信号3.两种不能被捕捉的信号 1.每种信号的含义 信号编号信号名信号含义1SIGHUP如果终端接口检测到一个连接断开,则会将此信号发送给与该终端相关的控制进程,该信号的默认处理动作是终止进程。2SIGINT当用户…...

Mac电脑交互式原型设计 Axure RP 8汉化最新 for mac
Axure RP 8是一款专业且快速的原型设计工具,主要用于定义需求、规格、设计功能和界面。这款工具主要适用于用户体验设计师、交互设计师、业务分析师、信息架构师、可用性专家和产品经理等职业。 Axure RP 8的主要特性包括能够快速设计出应用软件或Web网站的线框图、…...

在线免费无时长限制录屏工具 - 录猎在线版
需要录屏的小伙伴注意啦,想要长时间录制又不想花钱的,可以看下这款在线版录屏软件 —— 录猎在线版,一个录屏软件所需要的基本功能它都有,设置录制范围、录制的声音来源、摄像头也能录制的。同时它是支持Windows和Mac系统的&#…...

c语言文件操作详解:fgetc,fputc,fgets,fputs,fscanf,,fprintf,fread,fwrite的使用和区别
前言:在对于c语言的学习中,我们为了持续使用一些数据,为了让我们的数据可以在程序退出后仍然保存并且可以使用,我们引入了文件的概念和操作,本文旨在为大家分享在文件操作中常用的输入输出函数的使用方式和技巧&#x…...
Harmony装饰器
1、装饰器 装饰器是用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如: Component表示自定义组件Entry表示该自定义组件为入口组件State表示组件中的状态变量,状态变量变化会触发UI刷新。 2 、语法范式 Builder/BuilderParam&#…...

如何加快Chrome谷歌浏览器下载速度?
用Chrome打开chrome://flags/...

使用kubectl连接远程Kubernetes(k8s)集群
使用kubectl连接远程Kubernetes集群 环境准备下载kubectl下载地址 安装kubectl并处理配置文件Windows的安装配置安装kubectl拉取配置文件安装kubectl拉取配置文件kubectl命令自动补全 Linux的安装配置安装kubectl拉取配置文件kubectl命令自动补全 环境准备 你需要准备一个Kube…...

Kubernetes革命:云原生时代的应用编排和自动化
文章目录 什么是Kubernetes以及为何它备受欢迎?云原生应用和K8s的关系Kubernetes的核心概念:Pods、Services、ReplicaSets等部署、扩展和管理应用程序的自动化容器编排的演进:Docker到Kubernetes实际用例:企业如何受益于K8s的应用…...
mysql.mongoDb,neo4j数据库对比
#Mysql与MongoDb和Neo4j的一些对比 主要区别 MySQL: 1.MySQL是一种关系型数据库管理系统(RDBMS),广泛用于处理结构化数据。 2.它支持SQL语言,具备成熟的事务处理和数据一致性能力。 3.MySQL适用于大多数传统的基于表…...

unity使用UniStorm 5.1.0.unitypackage增加天气
添加天天气组件unistorm 然后添加一个player 导入包会报错,需要修改代码 using UnityEngine; using UnityEngine.PostProcessing;namespace UnityEditor.PostProcessing {[CustomPropertyDrawer(typeof(UnityEngine.PostProcessing.MinAttribute))]sealed class MinDrawer : …...

Flink实现kafka到kafka、kafka到doris的精准一次消费
1 流程图 2 Flink来源表建模 --来源-城市topic CREATE TABLE NJ_QL_JC_SSJC_SOURCE ( record string ) WITH (connector kafka,topic QL_JC_SSJC,properties.bootstrap.servers 172.*.*.*:9092,properties.group.id QL_JC_SSJC_NJ_QL_JC_SSJC_SOURCE,scan.startup.mode …...

Outlook屏蔽Jira AI提醒
前言:最近不知道为什么jira上的ai小助手抽风,一周发个几千封邮件…导致我现在都不想在邮箱里面跟找垃圾一样找消息了。实在忍无可忍,决定屏蔽AI小助手,方法很简单,follow me~~ 第一步:双击打开电脑版Outloo…...

毛玻璃 has 选择器卡片悬停效果
效果展示 页面结构 从上述的效果展示可以看到,页面是由多个卡片组成,并且鼠标悬停在卡片上时,会旋转用户图片并且韩式对应的用户信息框。 CSS3 知识点 :has 属性的运用 实现页面整体结构 <div class"container"><div…...
[hive]解决group by 字段超过系统规定64个
用开窗函数即可 ( row_number() over(partition by col1,...,col70 oder by xx) rn ) where rn1...
生成老年人的声音sox
sox laoren1.wav laoren2.wav pitch -300...

DC2DC电源设计注意事项--1,Feedback
电源采集图如下图 Feedback 采集电压点应该在靠近负载侧。这样可以减少大电流导线导致的电压差,真实反应输出电压值 FB_1P21采集电路靠近芯片侧, 2.1,采集分压电路上侧为Vout Vnoise, 那么一分压就噪声就小了。假如采集电路远离芯片侧&…...

计算机视觉处理的开源框架
计算机视觉是一门涉及图像和视频分析的领域,有许多开源的框架和库可用于构建计算机视觉应用程序。以下是一些常见的计算机视觉开源框架及其特点,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合…...

最新AI智能创作系统源码AI绘画系统/支持GPT联网提问/支持Prompt应用
AI绘图专业设计 不得将程序用作任何违法违纪内容,不要让亲人两行泪 界面部分图解构: 前台show: 前端部署: 安装pm2管理器 点击设置 选择v16.19.1版本-切换版本 再新建一个网站 点击设置 添加反向代理-代理名称随便…...
2019架构真题案例(四十八)
系统应用集成构件统一标准的基础平台,在各个应用系统的接口之间数据共享和功能,基本原则是保证应用程序的()。系统应用集成提供了四个不同层次的服务,最上层服务是()。 独立性相关性互操作性排…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...