Unity SRP学习笔记(二)

news2024/11/26 9:52:11

Unity SRP学习笔记(二)

主要参考:
https://catlikecoding.com/unity/tutorials/custom-srp/
https://docs.unity.cn/cn/2022.3/ScriptReference/index.html
中文教程部分参考(可选):
https://tuncle.blog/custom_render_pipeline/index.html
https://edu.uwa4d.com/lesson-detail/282/1308/0(依照Unity 2019版的内容翻译,不太适用于Unity 2022)
本文主要是,在参考以上内容学习的过程中,对一些基于已有内容但还是不太能理解的部分进行了一些额外的补充。

.Shader使用HLGL

当然也可以用GLSL在.Shader中写着色器,不过还是首推HLSL,毕竟绝大部分游戏都是在Windows平台下运行的。而且Shader中写的HLSL也不会直接执行,而是会根据目标平台编译成指定图形API。

SRP batcher

https://docs.unity.cn/cn/2019.4/Manual/SRPBatcher.html

初见SRPBatcher,了解到SPRBatcher可以通过合批优化渲染过程中CPU向GPU的数据发送环节。但也就仅限于此了,这种半懂不懂的感觉实在让人难受,于是便计划在实际项目中更深入的认识一下这到底是个什么玩意。
**主要有以下几个问题:**什么是通俗意义上的DrawCall?Unity渲染管线在最原始的情况下会如何进行渲染?经常出现在DrawCall优化中的方法——静态批处理/动态批处理/GPU Instancing都是什么?在Unity中实现后和原来相比到底什么区别?最后,再重新审视一下什么是SPR Batcher?

什么是通俗意义上的DrawCall?

你要写SRP batcher,就不能只写SRP batcher
参考了这篇文章DrawCall,Batches,SetPass calls是什么?,DrawCall就是一个CPU向GPU发出的渲染命令,同时告诉GPU我要渲染哪些数据。渲染管线进行一次渲染的步骤为:1)设置一个Shader为当前渲染状态(设置顶点着色器和片元着色器)。2)传递着色器参数,包括各种变换的矩阵(MVP),自定义的各类变量,以及纹理等。3)调用DrawCall,向GPU发出渲染命令。
文章中描述的一次渲染的流程和OpenGL完全对应的上🤗。
在OpenGL中,while (!glfwWindowShouldClose(window))所包围的部分就是完整的一帧渲染,而通常渲染一帧需要多次调用glDrawElements,glDrawElements就可以看成是DrawCall命令。进行一次glDrawElements,我们需要 1)用glUseProgram设置一个program为当前渲染环境(可选,如果不需要切换Shader就不需要再次设置)。2)设置着色器中Uniform的值,以及绑定VAO,设置layout,绑定纹理。3)调用画图函数(如glDrawElements)渲染目标,函数中指明了要渲染哪些数据。
了解了渲染管线基本步骤和DrawCall是什么,就该进到Unity里了。

为了进行后面的实验,需要能够控制渲染管线是否使用静态批处理/动态批处理/GPU Instancing/SPRBatcher。

Static Batching

我用的Unity版本是2022.3.45f1c1,平台为Windows10,静态批处理可以直接在ProjectSetting中设置。

在这里插入图片描述

Dynamic Batching/GPU Instancing

Dynamic Batching和GPU Instancing在SRP中,都需要在C#中手动设置,在URP中可以直接在内置的渲染管线中设置Dynamic Batching(该选项默认隐藏,需要开启全部可见),HDRP不支持Dynamic Batching。
通过对象初始化器{enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing}设置m_Flags的值。

DrawingSettings drawingOpaqueSettings = new DrawingSettings(unlitShaderTagId, sortingOpaqueSettings) {enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing };

这是因为Unity中通过变量m_Flags来控制Dynamic Batching/GPU Instancing的启用,m_Flags是一个枚举类型,其默认值为DrawRendererFlags.EnableInstancing。

internal enum DrawRendererFlags
{
    None = 0,
    EnableDynamicBatching = 1,
    EnableInstancing = 2
}

需要像URP一样在渲染管线中控制Dynamic Batching/GPU Instancing,需要在CustomRenderPipelineAsset中创建两个bool型变量并序列化,然后赋值到DrawingSettings中即可。具体实现在DrawCall后半章有。

疑问🤔:对已经通过某个变量完成实例化的类,在inspector中修改该变量为什么还能影响到已经完成实例化的类?
要么是检测如果有实例使用过该变量构造,则重新创建实例。
要么是Unity有什么特殊的办法能够修改readonly的变量。

public class CustomRenderPipelineAsset : RenderPipelineAsset
{
    [SerializeField]
    bool useDynamicBatching = true, useGPUInstancing = true, useSRPBatcher = true;
    protected override RenderPipeline CreatePipeline()
    {
        return new CustomRenderPipeline(useDynamicBatching, useGPUInstancing, useSRPBatcher);
    }
}
SRP Batcher

同样创建一个bool型变量并序列化,但不需要在DrawingSettings中设置,而只需要在CustomRenderPipeline.cs中设置Render函数的GraphicsSettings.useScriptableRenderPipelineBatching即可。

同时还需要提前将变量保存到常量缓冲区中:常量缓冲区介绍
常量缓冲区也需要字节对齐:cbuffer布局介绍,在Unity中如果不手动对齐的话,也能自动对齐。
在这里插入图片描述

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"可能会提示cannot open source file,说是历史遗留问题,除了写代码不太方便外不影响编译。
In Unity2018 and before, the editor feature ShaderIncludePath was used to configure the relative root of the shader, which is now obsolete.The current method is that #include “Packages//”,ShaderLab will parse and configure by itself when linking, but VS does not recognize this writing method and still links according to the default relative path of HLSL files, so an error will be reported, but compilation will not be affected.

以下代码会无法正常使用cbuffer,Shader文件会显示buildin property offset in cbuffer overlap other stages(UnityPerDraw)

CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
CBUFFER_END

UnityPerDraw必须定义特定的值组,cbuffer如下,这里的组名可以理解为更新方式,UnityPerDraw表示每次DrawCall更新,而unity_ObjectToWorld在不同DrawCall(即不同物体)时会改变,所以属于PerDraw。而相机的UnityPerFrame在每一帧内都是不变的,所以是PerFrame,不同材质对同一个Shader有不同的参数,即PerMaterial。在使用时Unity会自动检查所以不能混用,

CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_END

CBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_END

CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
CBUFFER_END

In this context, “update” refers to the process of initializing a constant buffer with data that will remain constant throughout the execution of the shader programs that access it. 基于上述内容理解,the execution of the shader programs可以理解为一次DrawCall的过程中cbuffer内的值会保持不变。

至此,在渲染管线中实现了对合批方法的控制。接下来用RenderDoc和Frame Debugger进行实验。

在这里插入图片描述

Unity渲染管线在最原始的情况下会如何进行渲染?

场景,40个物体(均使用默认网格),黄色为内置Shader Unlit/Color,绿色和红色为自定义Shader,仅材质参数BaseColor不同。
在这里插入图片描述
无合批优化情况下(RenderDoc能捕获到的内容)的Main Camera:

1)RSSetViewports 设置视口大小,ClearDepthStencilView 清除模板和深度缓冲。
2)绘制第一个物体,设置OM阶段混合状态,设置IA阶段顶点缓冲,布局及索引缓冲,设置OM阶段深度和模板缓冲状态,设置RS阶段光栅状态,设置VS和PS的着色器,UnityPerFrame,UnityPerDraw映射到CPU内存空间,修改后释放绑定到VS,UnityPerMaterial同样操作,绑定到PS,DrawIndexed调用绘制。
具体设置的情况在Pipeline State的OM阶段中可知。
在这里插入图片描述
cbuffer在Pipeline State的VS阶段和PS阶段的ConstantBuffers栏可见,需要HLSL中添加#pragma enable_d3d11_debug_symbols(提供有关着色器内部状态的附加信息),否则Slot只能看到cbuffer0,cbuffer1…
在这里插入图片描述
3)绘制下一个物体,结论为如果 a.同材质,无论是否使用同一个网格,都重新设置顶点,索引缓冲,修改UnityPerDraw。b.同Shader,不同材质,重新设置顶点,索引缓冲,修改UnityPerDraw和UnityPerMaterial。c.不同Shader,和第 2)步一样,所有内容重新设置。

上述是仅使用cbuffer情况下的渲染管线,如果把cbuffer去掉呢O.o。实际上如果去掉显式声明的cbuffer,Unity也会自动把所有的着色器参数放到为$Globals的常量缓冲区中。但是这样的话,因为每一次DrawCall都需要更新unity_ObjectToWorld的值,所以需要对整个Globals常量缓冲区进行映射,不好不好。
在这里插入图片描述

静态批处理/动态批处理/GPU Instancing

Static batching

静态批处理是一种绘制调用批处理方法,它将不会移动的网格组合在一起,以减少绘制调用。 它将组合网格转换到世界空间,并为它们建立一个共享顶点和索引缓冲区。 然后,对于可见的网格,Unity 会执行一系列简单的绘制调用,每次调用之间几乎不会改变状态。 静态批处理并不会减少绘制调用的次数,反而会减少它们之间渲染状态变化的次数。
Static batching会将不会移动的网格合并在一起(最主要的作用!),需要静态批处理的物体可在Inspector中设置为Stratic。
注意:Static Batching只有在Game窗口运行时才会生效!!!同时,因为是在运行模式下,默认的Clear flag:Skybox不会清除上一帧的颜色缓冲,虽然在运行时没有问题,但是会不便于观察调试,所以修改了一下让Skybox也清除颜色缓冲。
在这里插入图片描述
虽然文档说不会减少DrawCall,但是在RenderDoc中看DrawIndexed的次数是会减少的。Static batching只对同材质的物体渲染能有效提升(可不同Mesh)。如果材质不同,Mesh依然会合并且共用一个顶点缓冲区,但依然需要设置顶点缓冲,这样相对于不设置Static batching反而增加了每次DrawCall设置的顶点缓冲大小。
对于组合后的网格能否一次DrawCall绘制还得看是否在索引缓冲区上连续,如果不连续,即使是同材质的物体也需要多次DrawCall。但是不连续也只需要调用一次DrawIndexed,无需再次设置顶点缓冲和索引缓冲,渲染速度也是会更快的。

Dynamic batching

动态批处理是一种绘制调用批处理方法,可批处理移动的游戏对象以减少绘制调用。 动态批处理在网格和 Unity 在运行时动态生成的几何体(如粒子系统)之间的工作方式不同。 有关网格和动态几何体之间内部差异的信息,请参阅网格的动态批处理和动态生成几何体的动态批处理。 注:网格的动态批处理是为了优化老式低端设备的性能而设计的。 在现代消费级硬件上,动态批处理在 CPU 上的工作可能大于绘制调用的开销。 这会对性能产生负面影响。 更多信息,请参阅网格的动态批处理。
Dynamic batching会在每一帧动态计算哪些对象可以合批,哪些对象需要合批,针对需要合批的对象还需要在CPU中做模型变换(转到世界坐标下),这个是要CPU开销的。如果这个开销比不合批时多出来的DrawCall的开销还要大,就成负优化了(所以这种方法现在基本都不用了)。所以尽量还是用Static Batching吧,Static Batching虽然也要将合批对象在CPU中变换到世界坐标下组合,但是是在Build阶段实现的,而不是在运行时每一帧场景绘制之前。
在这里插入图片描述

GPU Instancing

GPU 实例化是一种绘制调用优化方法,可在一次绘制调用中渲染具有相同材质的多个网格副本。 每个网格副本称为一个实例。 这对于绘制场景中多次出现的物体非常有用,例如树木或灌木丛。 GPU 实例化可以在同一个绘制调用中渲染相同的网格。 为了增加变化和减少重复,每个实例可以有不同的属性,如颜色或比例。 渲染多个实例的绘制调用会在帧调试器中显示为 “渲染网格(实例化)”。
**用于优化相同材质,相同网格的物体渲染速度。**可以理解为,在所有相同材质,相同网格的物体中取其中一个实例,对于其他物体将会变化的部分以数组的方式存储在GPU常量缓冲区中,每个物体都有一个唯一InstanceID,通过这个ID就可以索引其数据在数组中的数据。
例如,假如每个实例在世界坐标下的位置(即模型变换矩阵)都不一样,那么GPU中就会有一个常量缓冲区(名为PerDraw0)用来存储其模型变换矩阵数组。启用GPU Instancing时,顶点着色器中传入的数据(即Attributes)带有UNITY_VERTEX_INPUT_INSTANCE_ID,通过UNITY_SETUP_INSTANCE_ID(input)设置InstanceID后GetObjectToWorldMatrix()就能获取对应实例的模型变换矩阵(这个部分在UnityInstancing.hlsl中实现,当然如果想变的麻烦的话也可以自己实现。比如这里的_BaseColor就是自己实现的,可以对不同实例实现不同颜色参数)。
PS:使用_BaseColor时,如果想改颜色需要用MaterialPropertyBlock(教程默认用这个,可能不会遇到这种问题),而不是直接改Material。因为直接改Material会创建新的材质,而MaterialPropertyBlock是重写材质,还是原来的材质所以符合使用相同材质的条件。

//.hlsl
//避免多次include导致重定义问题
#ifndef CUSTOM_UNLIT_SHADER

#define CUSTOM_UNLIT_SHADER


//包含了对cbuffer的宏定义替换
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"

#define UNITY_MATRIX_M unity_ObjectToWorld;

//包含了对GPU实例所需的很多内容,可以简单的使用GPU Instancing
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"

CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_END

float4x4 GetObjectToWorldMatrix()
{
    return UNITY_MATRIX_M;
}

CBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_END

UNITY_INSTANCING_BUFFER_START(BaseColorCB)  //可随意命名,用于在UNITY_ACCESS_INSTANCED_PROP中使用
    UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(BaseColorCB)

struct Attributes
{
    UNITY_VERTEX_INPUT_INSTANCE_ID
    float3 positionOS : POSITION;
};

struct Varyings
{
    UNITY_VERTEX_INPUT_INSTANCE_ID
    float4 positionCS : SV_POSITION;
};

Varyings UnlitPassVertex(Attributes input)
{
    Varyings output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output); //因为这里的片元着色器中有根据InstanceID设置的变量所以需要传递InstanceID,如果没有可以去掉这部分
    output.positionCS = mul(unity_MatrixVP, mul(GetObjectToWorldMatrix(), float4(input.positionOS, 1.0)));
    return output;
}

float4 UnlitPassFragment(Varyings input) : SV_TARGET
{
    UNITY_SETUP_INSTANCE_ID(input);
    return UNITY_ACCESS_INSTANCED_PROP(BaseColorCB, _BaseColor); //根据InstanceID在名为UnityPerMaterial的常量缓冲区中的_BaseColor数组内知道当前实例的_BaseColor
}

#endif

综上所述,GPU Instancing其实就是把使用同一个Mesh和同一个材质的所有对象会变化的部分以数组的形式存储在常量缓冲区中,再通过InstanceID进行索引。同一个Mesh保证传入的顶点坐标是通用的(即在每个实例渲染中,Attributes的positionOS是通用的),同一个材质大概是确保无需切换渲染状态吧。所以GPU Instancing可以规避合并Mesh导致的内存与性能上升的问题。例如对于大量的树木或者杂草,使用GPU Instancing不会像Static Batching会导致生成一个巨大的顶点缓冲和索引缓冲,只需将模型变换矩阵以数组的形式加载到常量缓冲区中即可。
同样,常量缓冲区是有大小限制的(64kb),Unity中也限制了UNITY_MAX_INSTANCE_COUNT为500,最多一次DrawCall支持500个实例。
【Unity笔记】ShaderLab与其底层原理浅谈

SRP Batcher

Unity SRP Batcher的工作原理
关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析
内容可以参照这些文章👆,相比于GPU Instancing,SPR Batcher不会减少DrawCall,但条件更加宽松,只要是同一个Shader即可。
**数据提前加载到GPU:**从RenderDoc中看,所有需要进行渲染的对象的各种信息已经提前保存在GPU缓冲区中(例如顶点信息【不过就算不使用SRP Batcher,默认网格的顶点数据也会在初始化的时候加载到GPU】,模型变换矩阵,材质参数等)了,在DrawCall的准备阶段只需要进行绑定。即便DrawCall没有减少,但是由于每次DrawCall都只需要绑定资源,同时DrawCall本身也只是个绘制调用,所以每次DrawCall的时间大大降低了。
在这里插入图片描述
**数据不再每帧被重新创建:**这部分在RenderDoc中没能找到佐证的部分,可能是我用的方法有问题。
❗使用MaterialPropertyBlock重写的材质无法被SRP Bathcer合批处理。

unity/tutorials/custom-srp/draw-calls

简单看下结果,使用GPU Instancing绘制大量网格,只需要3个Batches即可绘制完毕。

在这里插入图片描述

不使用GPU Instancing,需要501次Batches(500次绘制球,一次绘制天空盒)。

在这里插入图片描述

cutoff / blend

在这里插入图片描述

❗记得在新创建的材质中勾选Enable GPU Instancing。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2236337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

帮你快速理解并巧记设计模式

经常因为记不住或不能理解设计模式而苦恼的童鞋们注意了,闲暇之余总结了常用的22中设计模式,并一一举例,帮助大家快速理解、牢记,如有不对的地方,欢迎大家指正哈 创建型模式 单例模式(Singleton Pattern&…

STM32CUBEIDE FreeRTOS操作教程(八):queues多队列

STM32CUBEIDE FreeRTOS操作教程(八):queues多队列 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件,不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例&#…

防火墙|WAF|漏洞|网络安全

防火墙|WAF|漏洞|网络安全 防火墙 根据内容分析数据包: 1、源IP和目的IP地址 2、有效负载中的内容。 3、数据包协议(例如,连接是否使用 TCP/IP 协议)。 4、应用协议(HTTP、Telnet、FTP、DNS、SSH 等)。 5…

【04】【Maven项目热部署】将Maven项目热部署到远程tomcat服务器上

1.虽然现在Maven中央仓库中支持的tomcat插件只支持到tomcat7这个版本,但是可以利用这个插件对Web项目进行热部署,热部署到远程服务器的tomcat服务器上,远程服务器上的tomcat版本可以是更高的版本,比如说tomcat8、9、10或更高的版本…

大数据新视界 -- 大数据大厂之 Impala 性能优化:融合机器学习的未来之路(上 (2-1))(11/30)

💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

解决:使用EasyExcel导入Excel模板时出现数据导入不进去的问题

解决:使用EasyExcel导入Excel模板时出现数据导入不进去的问题 在Java中,当我们用EasyExcel导入Excel时,可能会出现数据导入不进去的问题。例如: 这种异常等。 问题原因1:这个1代表从第几行开始,你的exce…

在vscode中开发运行uni-app项目

确保电脑已经安装配置好了node、vue等相关环境依赖 进行项目的创建 vue create -p dcloudio/uni-preset-vue 项目名 vue create -p dcloudio/uni-preset-vue uni-app 选择模版 这里选择【默认模版】 项目创建成功后在vscode中打开 第一次打开项目 pages.json 文件会报错&a…

如何修改WordPress经典编辑器的默认高度?

boke112百科有一个使用WordPress搭建的小网站,文章内容就是几个字不到一行,但是每次使用经典编辑器编辑文章时,都觉得编辑器默认高度太高了,影响了我添加文章摘要和其他属性,有没有办法修改WordPress经典编辑器的默认高…

汽车广告常见特效处理有哪些?

​汽车广告作为展示汽车性能和外观的重要媒介,常常需要借助特效来增强视觉效果,吸引观众的注意力。以下是一篇关于汽车广告中常见特效处理的文章。 在竞争激烈的汽车市场中,广告不仅是推广产品的工具,更是艺术和科技的结合。特效技…

从0开始学习机器学习--Day19--学习曲线

一般来说,如果一个算法的表现不理想,那么多半是因为出现了欠拟合或过拟合问题,这种时候我们要做的就是搞清楚出现的是偏差问题还是方差问题,亦或是二者皆有,这有助于我们精准定位问题所在。 之前,我们发现…

HarmonyOS NEXT 应用开发实战(九、知乎日报项目详情页实现详细介绍)

在本篇博文中,我们将探讨如何使用 HarmonyOS Next 框架开发一个知乎日报的详情页,逐步介绍所用到的组件及代码实现。知乎日报是个小巧完整的小项目,这是一个循序渐进的过程,适合初学者和有一定开发经验的工程师参考。 1. 项目背景…

LLM训练”中的“分布式训练并行技术;分布式训练并行技术

目录 “LLM训练”中的“分布式训练并行技术” 分布式训练并行技术 数据并行 流水线并行:按阶段(stage)进行切分 张量并行 序列并行 多维混合并行 自动并行 MOE并行 重要的分布式AI框架 “LLM训练”中的“分布式训练并行技术” 随着深度学习技术的不断发展,特别是…

论文阅读:Computational Long Exposure Mobile Photography (二)

这篇文章是谷歌发表在 2023 ACM transaction on Graphic 上的一篇文章,介绍如何在手机摄影中实现长曝光的一些拍摄效果。 Abstract 长曝光摄影能拍出令人惊叹的影像,用运动模糊来呈现场景中的移动元素。它通常有两种模式,分别产生前景模糊或…

测试概念以及测试bug

关于测试的概念 什么是需求? 需求分为用户需求和软件需求。 软件需求可以作为开发和测试工作的依据,而用户需求不一定是合理的,这里的不合理有很多的角度:技术角度上,市场需求上,投入成本和收益比噔噔。…

mac m1 docker本地部署canal 监听mysql的binglog日志

mac m1 docker本地部署canal监听mysql的binglog日志(虚拟机同理) 根据黑马视频部署 1.docker 部署mysql 1.docker拉取mysql 镜像 因为m1是arm架构.需要多加一条信息 正常拉取 docker pull mysql:tagm1拉取 5.7的版本. tag需要自己指定版本 docker pull --platform linux/x…

C++之旅——入门

Hello,各位小伙伴们,前几期我们学习了C语言和数据结构,有了一定的基础之后我们来挑战新的语言——C。 目录 一、初识C 1>C发展历史 2>C版本更新 3>C的热度 4>C的学习难度 5>C学习相关书籍 二、第一个C代码 三、C基础 …

Flutter中文字体设置指南:打造个性化的应用体验

在使用Flutter进行开发时,可能会遇到中文字体显示不正常或者字体不符合设计需求的情况。Flutter默认的中文字体往往无法满足某些用户对个性化和美观的需求。今天,我们就来详细探讨如何在Flutter应用中设置中文字体,并结合不同场景提供相应的解…

4.1 软件设计概要

软件设计概要 1、 软件设计的概念和设计质量1.1 软件设计基本任务1.2 设计模型1.3 软件设计特点1.4 设计质量属性1.5 设计指导原则 2、 设计相关八大概念抽象体系结构设计模式模块化信息隐藏功能独立精化重构 3、 四类设计技术概要3.1 数据设计3.2 体系架构设计体系结构组织和细…

MySQL表的增删改查(CRUD2)

美好的一天又开始了,大家今天有没有学习呢?没学的话,开始跟随和博主一起对MYSQL的学习吧!!! 复习: CRUD新增1.新增 insert into 表名 [(列名[,列名,列名...])] values (值[,值,值.…

用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(三)

概述 从 WWDC 24 开始,苹果推出了全新的测试机制:Swift Testing。利用它我们可以大幅度简化之前“老态龙钟”的 XCTest 编码范式,并且使得单元测试更加灵动自由,更符合 Swift 语言的优雅品味。 在这里我们会和大家一起初涉并领略…